Module:CosmeticInfo
From MCC Island Wiki
More actions
This module uses info gathered from the API and stored in the data page to:
- greatly simplify creating pages for cosmetics via Template:Cosmetic
- generate the bulk of the Tradeable Cosmetics page
- output lists of cosmetics based on particular field values
It is also invoked by other modules, such as Module:CosmeticMachine and Module:CosmeticCrates.
local getArgs = require('Module:Arguments').getArgs
local expand = require('Module:Utils').expand
local preprocess = require('Module:Utils').preprocess
local isAnimated = require('Module:Utils').isAnimated
local currentSeason = require("Module:CurrentSeason")
local cosmeticData = mw.loadData('Module:CosmeticInfo/Data')
if not cosmeticData then
return "Error: Unable to load data"
end
local obtainabilityData = mw.loadData("Module:CosmeticInfo/Obtainability/Data")
local defaultChromaSetsData = mw.loadData("Module:CosmeticInfo/DefaultChromaSets/Data")
local rotatingWeaponForgeData = mw.loadData("Module:RotatingWeaponForge/Data")
local limitedWeaponForgeData = mw.loadData("Module:LimitedWeaponForge/Data")
local tradingData = require('Module:TradingData').tradableAssets
local today = os.date("%Y-%m-%d %H:%M:%S")
local chromaSets = mw.loadData("Module:CosmeticInfo/ChromaSets/Data")
local p = {}
local function getCosmetic(args)
return cosmeticData["cosmetics"][args.name]
end
local function getSimpleField(field)
return function(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
return cosmetic and cosmetic[field]
end
end
local function makeSet(list)
local t = {}
for _, v in ipairs(list) do t[v] = true end
return t
end
local function isACosmetic(rule)
local collections = rule.collections and makeSet(rule.collections)
return function(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
if not cosmetic then return false end
if collections and not collections[p.getCollection({name=args.name})] then
return false
end
if rule.type and p.getType({name=args.name}) ~= rule.type then
return false
end
if rule.notType and p.getType({name=args.name}) == rule.notType then
return false
end
return true
end
end
local isACosmeticRules = {
isCrateCosmetic = {
collections = {
"Elemental", "Standard Game", "Exclusive Game",
"Exclusive Season", "Exclusive Variety", "Gate"
},
notType = "Arcane"
},
isArcaneGateCosmetic = {
collections = { "Gate" },
type = "Arcane"
},
isFishingCosmetic = {
collections = { "Fishing" }
},
isWeaponSkin = {
collections = { "Premium Weapon", "Limited Weapon" }
}
}
for key, rule in pairs(isACosmeticRules) do
p[key] = isACosmetic(rule)
end
local function formatGlyphs(description)
local glyphs = {
["<glyph:\"mcc:icon.tooltips.veteran\"> "] = "<br />[[File:Icon-Veteran.png|16px]] <span style=\"color: #52C8FF; font-weight: bold\">",
["<glyph:\"mcc:icon.tooltips.squidtek\"> "] = "<br />[[File:Icon-Squidtek.png|16px]] <span style=\"color: #52C8FF; font-weight: bold\">",
["<glyph:\"mcc:icon.info_blue\"> "] = "<br />[[File:Icon-Info-Blue.png|16px]] <span style=\"color: #55FF55; font-weight: bold\">",
}
local openSpan = false
for key, val in pairs(glyphs) do
if description:find(key) then
description = description:gsub(key, (openSpan and val or "</span>" .. val))
openSpan = true
end
end
return openSpan and (description .. "</span>") or nil
end
local function formatArcaneBonus(description)
if description:find("Arcane Bonus") then
description = description:gsub("Arcane Bonus", "<br /><span style=\"color: #64FCFC\"><span class=\"white-text\">Arcane Bonus</span>")
description = description:gsub("(Grants you)(.-)( while)", function(startText, middle, endText)
return startText .. "<span class=\"white-text\">" .. middle .. "</span>" .. endText
end)
return description .. "</span>"
end
return description
end
local function stripDescription(description)
description = description:gsub("<glyph:\".-\"> ?", "")
description = description:gsub("Arcane Bonus.*", "")
return mw.text.trim(description)
end
function p.getDescription(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
if not cosmetic then return "No data found" end
local plain = args.plain == "true"
if plain then
return stripDescription(cosmetic.description)
end
return formatGlyphs(cosmetic.description) or formatArcaneBonus(cosmetic.description)
end
p.getCategory = getSimpleField("category")
p.getCollection = getSimpleField("collection")
p.getRarity = getSimpleField("rarity")
p.isColorable = getSimpleField("colorable")
p.getTrophiesAwarded = getSimpleField("trophies")
p.isBonusTrophies = getSimpleField("isBonusTrophies")
p.canBeDonated = getSimpleField("canBeDonated")
function p.getDonationLimit(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
return (cosmetic and cosmetic.royalReputation and cosmetic.royalReputation.donationLimit) or 0
end
function p.getReputationAmount(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
return (cosmetic and cosmetic.royalReputation and cosmetic.royalReputation.reputationAmount) or 0
end
p.getGlobalNumberOwned = getSimpleField("globalNumberOwned")
p.getObtainmentHint = getSimpleField("obtainmentHint")
function firstToUpper(str)
return (str:gsub("^%l", string.upper))
end
function p.getType(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
return firstToUpper(cosmetic["type"]:lower())
end
function p.getFishingClimate(frame)
local args = getArgs(frame)
local cosmetic = getCosmetic(args)
if not cosmetic or not p.isFishingCosmetic({name = args.name}) then
return nil
end
local hint = cosmetic.obtainmentHint or ""
local climate = hint:match("while fishing in the (.-) Climate")
if climate then
return firstToUpper(climate)
end
return nil
end
function p.getLastUpdatedDate()
local lang = mw.language.getContentLanguage()
return lang:formatDate( 'd F Y, h:i a', '@' .. cosmeticData['last_updated'], false ) .. ' UTC'
end
function p.lastUpdatedIcon()
return string.format(
'<sup><abbr title="Last updated: %s" tabindex="0">ⓘ</abbr></sup>',
p.getLastUpdatedDate()
)
end
function p.getDefaultChromaSet(frame)
local args = getArgs(frame)
return defaultChromaSetsData[args.name] or ""
end
local function isCurrentlyInLimitedForge(name)
for _, rotation in ipairs(limitedWeaponForgeData) do
if today >= rotation.start_date and today <= rotation.end_date then
for _, skin in ipairs(rotation.skins) do
if skin == name then return true end
end
end
end
return false
end
local function isCurrentlyInRotatingForge(name)
local rotationToday = rotatingWeaponForgeData.days[rotatingWeaponForgeData.current_day] or {}
for _, rotationSkin in ipairs(rotationToday) do
if rotationSkin == name then return true end
end
return false
end
local function getOwnedNumber(raw)
raw = tostring(raw or "0"):gsub(",", ""):gsub("%+", "")
return tonumber(raw) or 0
end
local function sortCosmetics(list, order)
table.sort(list, function(a, b)
return order == "desc" and a.owned > b.owned or a.owned < b.owned
end)
end
local function formatCosmeticList(cosmetics)
local result = {}
for _, c in ipairs(cosmetics) do
table.insert(result, string.format("* %s (%s)", c.name, c.display or c.owned))
end
return table.concat(result, "\n")
end
function p.sortListByNumberOwned(frame)
local args = getArgs(frame)
local names = mw.text.split(args.names or "", ",%s*")
local order = args.order or "asc"
local unformatted = args.unformatted or ""
local cosmetics = {}
for _, name in ipairs(names) do
local data = cosmeticData["cosmetics"][name]
if data and data.globalNumberOwned then
table.insert(cosmetics, {
name = name,
owned = getOwnedNumber(data.globalNumberOwned),
display = data.globalNumberOwned
})
end
end
sortCosmetics(cosmetics, order)
if unformatted ~= "" then
return cosmetics
else
return formatCosmeticList(cosmetics)
end
end
function p.getNumberOwnedListByFieldValue(frame)
local args = getArgs(frame)
local field = args.field
local target = args.value
local order = args.order or "asc"
local filterValue = ({
["true"] = true,
["false"] = false
})[target] or target
local filtered = {}
for name, data in pairs(cosmeticData["cosmetics"]) do
if data[field] == filterValue and data.globalNumberOwned then
table.insert(filtered, {
name = name,
owned = getOwnedNumber(data.globalNumberOwned),
display = data.globalNumberOwned
})
end
end
sortCosmetics(filtered, order)
return formatCosmeticList(filtered)
end
function p.tradeablesPage(frame)
local function getGroup(cosmetic)
local categories = {}
local obtainability = obtainabilityData[cosmetic.name]
local cType = p.getType({name = cosmetic.name})
local collection = p.getCollection({name = cosmetic.name})
if obtainability == "Grand Auction" then
table.insert(categories, obtainability)
elseif obtainability == "Returning" then
table.insert(categories, "Returning in a future event")
elseif obtainability == "Unobtainable" then
table.insert(categories, "Unobtainable other than trading")
elseif obtainability == "Limited Weapon Forge" then
table.insert(categories, "Unobtainable once the [[Limited Weapon Forge]] rotates")
elseif p.isWeaponSkin({ name = cosmetic.name }) then
if isCurrentlyInRotatingForge(cosmetic.name) then
table.insert(categories, "Currently available in the [[Rotating Weapon Forge]]")
end
if isCurrentlyInLimitedForge(cosmetic.name) then
table.insert(categories, "Currently available in the [[Limited Weapon Forge]]")
end
if #categories == 0 then
table.insert(categories, "Currently only obtainable from [[Premium Weapon Crate]]s")
end
elseif cType == "Collector" then
table.insert(categories, cType)
elseif obtainability ~= nil then
table.insert(categories, string.format("Unobtainable once the current [[%s]] event ends", obtainability))
else
table.insert(categories, "Unobtainable once [[" .. currentSeason .. "|this season]] ends")
end
return categories
end
local function getOwnershipBand(owned)
if owned == "10000+" or owned == "1000+" then
return owned .. " owned"
else
return "Less than 1000 owned"
end
end
local function groupCosmetics(cosmeticList)
local grouped = {}
for _, cosmetic in ipairs(cosmeticList) do
local categories = getGroup(cosmetic)
local band = getOwnershipBand(cosmetic.display)
for _, category in ipairs(categories) do
grouped[category] = grouped[category] or {}
grouped[category][band] = grouped[category][band] or {}
table.insert(grouped[category][band], cosmetic)
end
end
return grouped
end
local function renderTableForGroup(group, title)
local out = {'<h2>' .. title .. '</h2>'}
local bandOrder = { "Less than 1000 owned", "1000+ owned", "10000+ owned" }
local bandCount = 0
for _, band in ipairs(bandOrder) do
if group[band] then
bandCount = bandCount + 1
end
end
for _, band in ipairs(bandOrder) do
local list = group[band]
if list then
sortCosmetics(list)
if bandCount > 1 then
table.insert(out, '<h3>' .. band .. '</h3>')
end
table.insert(out, '<div class="tradeable-display">')
local count = 0
for _, item in ipairs(list) do
if count > 0 and count % 4 == 0 then
--table.insert(out, '<div>')
end
local name, owned = item.name, item.display
local filename = name .. '.png'
local cell
if band == "Less than 1000 owned" then
cell = string.format('<div><center><div class="afix" style="width: 100px">[[File:%s|link=%s]]</div></center><div style="word-wrap:break-word; text-align:center"><small>[[%s]]</small><br />Owned: <b>%s</b></div></div>', filename, name, name, owned)
else
cell = string.format('<div><center><div class="afix" style="width: 100px">[[File:%s|link=%s]]</div></center><div style="word-wrap:break-word; text-align:center"><small>[[%s]]</small></div></div>', filename, name, name)
end
table.insert(out, cell)
count = count + 1
end
table.insert(out, '</div>')
end
end
return table.concat(out, '\n')
end
local tradeables = {}
for name, _ in pairs(cosmeticData["cosmetics"]) do
local cType = p.getType({ name = name })
if cType == "Limited" or cType == "Premium" or cType == "Collector" then
local data = cosmeticData["cosmetics"][name]
if data and data.globalNumberOwned then
table.insert(tradeables, {
name = name,
display = data.globalNumberOwned,
owned = getOwnedNumber(data.globalNumberOwned)
})
end
end
end
local grouped = groupCosmetics(tradeables)
local output = {}
local categoryOrder = {
"Unobtainable other than trading",
"Collector",
"Grand Auction",
"Currently available in the [[Rotating Weapon Forge]]",
"Currently available in the [[Limited Weapon Forge]]",
"Currently only obtainable from [[Premium Weapon Crate]]s",
"Unobtainable once the [[Limited Weapon Forge]] rotates",
"Returning in a future event"
}
for category, _ in pairs(grouped) do
if category:match("^Unobtainable once the current %[") then
table.insert(categoryOrder, category)
end
end
table.insert(categoryOrder, "Unobtainable once [[" .. currentSeason .. "|this season]] ends")
for _, category in ipairs(categoryOrder) do
if grouped[category] then
table.insert(output, renderTableForGroup(grouped[category], category))
end
end
return table.concat(output, '\n\n')
end
function p.getInfoboxImage(frame)
local args = getArgs(frame)
local name = args.name
local isWeaponSkin = p.isWeaponSkin(frame)
local rarity = p.getRarity(frame)
if isWeaponSkin == true and rarity == "Mythic" then
return frame:callParserFunction('#tag:gallery', {
name .. '.png|Normal\n' .. name .. ' (Evolved).png|Evolved'
})
elseif mw.title.new("File:" .. name .. ".png").exists then
return name .. ".png"
end
end
local function renderWeaponGrid(frame, list, useChromaTemplate, addSpacing)
local out = {}
if addSpacing then
table.insert(out, '<div class="tradeable-display" style="margin-top: 1rem">')
else
table.insert(out, '<div class="tradeable-display">')
end
for _, item in ipairs(list) do
local label
if useChromaTemplate then
label = expand(frame, "Chroma Set", item.label, "short=yes")
else
label = item.label
end
local spacing = addSpacing and ' style="margin-bottom:18px; width: 20% !important"' or ""
table.insert(out, string.format(
'<div%s><center><div class="afix" style="width: 100px">[[File:%s|link=%s]]</div></center><div style="text-align:center">%s</div></div>',
spacing, item.file, item.link, label
))
end
table.insert(out, '</div>')
return table.concat(out, "\n")
end
function p.getChromasForWeapon(frame)
local args = getArgs(frame)
local name = args.name
if not p.isWeaponSkin({name = name}) then
return "Not a weapon skin"
end
local rarity = p.getRarity({name = name})
local defaultChroma = defaultChromaSetsData[name]
local chromas = {}
for _, chromaName in pairs(chromaSets) do
table.insert(chromas, chromaName)
end
table.sort(chromas)
if rarity == "Mythic" then
local normalList = {}
local evolvedList = {}
for _, chroma in ipairs(chromas) do
local normalFile
local evolvedFile
if chroma == defaultChroma then
normalFile = name .. ".png"
evolvedFile = name .. " (Evolved).png"
else
normalFile = string.format("%s (%s).png", name, chroma)
evolvedFile = string.format("%s (%s - Evolved).png", name, chroma)
end
table.insert(normalList, {
file = normalFile,
link = name,
label = chroma
})
table.insert(evolvedList, {
file = evolvedFile,
link = name,
label = chroma
})
end
local wikitext = table.concat({
"<tabber>",
"|-|Normal=\n" .. renderWeaponGrid(frame, normalList, true, true),
"|-|Evolved=\n" .. renderWeaponGrid(frame, evolvedList, true, true),
"</tabber>"
}, "\n")
return preprocess(frame, wikitext)
end
local list = {}
for _, chroma in ipairs(chromas) do
local file
if chroma == defaultChroma then
file = name .. ".png"
else
file = string.format("%s (%s).png", name, chroma)
end
table.insert(list, {
file = file,
link = name,
label = chroma
})
end
return renderWeaponGrid(frame, list, true, true)
end
function p.getWeaponsForChromaSet(frame)
local args = getArgs(frame)
local chroma = args.chroma_set
local groups = {}
for name, data in pairs(cosmeticData.cosmetics) do
if p.isWeaponSkin({name = name}) then
local defaultChroma = defaultChromaSetsData[name]
local file
if chroma == defaultChroma then
file = name .. ".png"
else
file = string.format("%s (%s).png", name, chroma)
end
local rarity = p.getRarity({name = name})
groups[rarity] = groups[rarity] or {}
table.insert(groups[rarity], {
file = file,
link = name,
label = '[[' .. name .. ']]'
})
end
end
local rarityOrder = { "Mythic", "Legendary", "Epic", "Rare" }
local out = {}
for _, rarity in ipairs(rarityOrder) do
table.insert(out, string.format("=== %s ===", expand(frame, "Rarity", rarity, "Text")))
local list = groups[rarity]
table.sort(list, function(a, b) return a.label < b.label end)
table.insert(out, renderWeaponGrid(frame, list, false, false))
end
return table.concat(out, "\n")
end
return p