Viewing old revision of Module:Trade_Items
You are viewing an old revision of this page from 2/26/2026, 11:31:24 PM.
View latest versionconst 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 "Empty";
const { data, version } = await Utils.resolveData("Trade_Items", false);
if (Utils.isModuleEmpty(data)) {
return '⚠️ Trade items data are unavailable for all known versions.';
}
const itemId = cacheKey.replace(/\s+/g, '_');
const itemData = data[itemId];
if (!itemData) {
return `⚠️ Trade item '${itemArg}' not found in data version ${version}.`;
}
const tooltip = new TooltipBuilder(`TradeItem:Item:${itemId}`);
tooltip.addLine(
`<div style="display:flex;justify-content:space-between;">` +
`<div>'''${itemData.name || itemArg}'''</div>` +
`<div>[[File:${itemData.id}_icon.png|26px|inline|alt=${itemData.name || itemArg}]]</div>` +
`</div>`
);
if (itemData.description) {
const lines = itemData.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>"
)
);
}
}
const result =
`<span data-tooltip-id="${tooltip.id}">` +
`[[Trade Items/${itemData.name || itemArg}|${itemData.name || itemArg}]]` +
`</span>` +
tooltip.build();
return String(frame)
return result;
}
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>`;
}
exports = {
wikiTable,
wikiNavbox,
wikiTooltip,
wikiInfobox,
wikiDropSources,
wikiShopSources
}