Cozy Grove Wiki
This module is invoked by the following templates:

It uses these submodules:

  • /Data - Individual companion hat information and pricing information based on rarity

local arguments = require("Module:Arguments")

-- Note: Comments are Lua Language Server Annotations
-- Annotations allow for type checking and autocomplete in VSCode and other IDEs with the appropriate extensions.

--- @type CompanionHatData
local data = mw.loadData("Module:Camp_Spirit/Companion/Hat/Data")

local p = {}
--- @param hat CompanionHat The companion hat data
--- @return (string | nil)  The color to use as the main icon. nil for rare or common with one color
local function getColorToShow(hat)
	if hat.rarity == "Rare" or (hat.rarity == "Common" and hat.colors == "One") then
		return nil
	end
	local cts = hat.color_to_show or "Inky Purple"
	return cts
end

local function formatHatData(name, frame)
    local lang = mw.getContentLanguage()
	local fromData = data.hats[name]

    local hatData = {
        name = name,
        buy = lang:formatNum(data.prices.buy[string.lower(fromData.rarity)]) .. frame:expandTemplate{title = "Coin/Camp Spirit"},
        sell = lang:formatNum(data.prices.sell[string.lower(fromData.rarity)]) .. frame:expandTemplate{title = "Coin/Camp Spirit"},
    }
    
    for k, v in pairs(fromData) do
    	-- Preprocess the description so it doesn't show nowiki/br tags
    	if k == "description" then
    		hatData[k] = frame:preprocess(v)
		else
	        hatData[k] = v
		end
    end
    
	hatData.color_to_show = getColorToShow(hatData)
    
    -- Add icon file name without File: or .png to use with image template
    if string.lower(fromData.rarity) == "rare" or (string.lower(fromData.rarity) == "common" and string.lower(fromData.colors) == "one") then
    	hatData.icon = string.lower(hatData.name .. " " .. hatData.rarity)
	else
    	hatData.icon = string.lower(hatData.name .. " " .. hatData.rarity .. " " .. hatData.color_to_show)
	end
    -- Common hats with one color use the higher rare price
    if string.lower(fromData.rarity) == "common" and string.lower(fromData.colors) == "one" then
    	hatData.buy = data.prices.buy.rare .. frame:expandTemplate{title = 'Coin/Camp Spirit'}
    	hatData.sell = data.prices.sell.rare .. frame:expandTemplate{title = 'Coin/Camp Spirit'}
    end
	
	return hatData
end

--- @class GetPriceArgs
--- @field type ("buy" | "sell" | nil) The type of price to return. Defaults to sell.
--- @field rarity (Rarity | nil) Rarity of the companion hat. Defaults to Common.

--- @class GetPriceFrame
--- @field args GetPriceArgs

--- Retrieves a companion hat price by rarity and price type
--- @param frame GetPriceFrame MediaWiki frame
--- @return number price Companion hat price or -1 if not found
function p.GetPrice(frame)
    local args = arguments.getArgs(frame)
    local priceType = string.lower(args["type"] or "sell")
    local rarity = string.lower(args["rarity"] or "Common")

    local rarities = {
        ["common"] = true,
        ["rare"] = true
    }

    local isValidRarity = rarities[rarity] ~= nil

    if not isValidRarity then
        error("Rarity does not match one of (Common|Rare)")
        return -1
    end

    local priceTypes = {
        ["buy"] = true,
        ["sell"] = true
    }

    local isValidType = priceTypes[string.lower(priceType)] ~= nil

    if not isValidType then
        error("Type does not match one of (buy|sell)")
        return -1
    end


    return data.prices[priceType][rarity]
end

--- @class GetHatsArgs
--- @field rarity (Rarity | nil) The rarity of the hats to return. Defaults to nil and returns all rarities.
--- @field season (Season | nil) The season of the hats to return. Defaults to nil and returns all seasons.

--- Retrieves a list of page names that match a rarity and/or season
--- @param frame table MediaWiki frame
--- @return string A comma-separated list of page names that match the filters
function p.GetPageList(frame)
    local args = arguments.getArgs(frame)
    --- @type (Rarity | nil)
    local rarity = args.rarity or nil
    --- @type (Season | nil)
    local season = args.season or nil

    local hatList = {}

    if rarity ~= nil then
        for k, v in pairs(data.hats) do
            if v.rarity == rarity then
                if season ~= nil then
                    if v.season == season then
                        table.insert(hatList, k .. " (Camp Spirit)")
                    end
                else
                    table.insert(hatList, k .. " (Camp Spirit)")
                end
            end
        end
    elseif season ~= nil then
        for k, v in pairs(data.hats) do
            if v.season == season then
                table.insert(hatList, k .. " (Camp Spirit)")
            end
        end
    else
        for k, _ in pairs(data.hats) do
            table.insert(hatList, k .. " (Camp Spirit)")
        end
    end

    table.sort(hatList)

    return table.concat(hatList, ",")
end

--- @param frame table MediaWiki frame
function p.Infobox(frame)
    local lang = mw.getContentLanguage()
    local args = arguments.getArgs(frame)
    
    local hatData = formatHatData(args["name"], frame)

    return frame:expandTemplate { title = "Companion Clothing Infobox/Camp Spirit", args = hatData }
end

function p.Table(frame)
    local tableBuilder = require("Module:TableBuilder")
    local args = arguments.getArgs(frame)
    local list = args["list"] or args[1]

    if list == nil or #list == 0 then
        error("Must provide a list of page names")
    end
    local rawCols = args["columns"] or "name"

    local columns = {}
    for col in string.gmatch(rawCols, '([^,]+)') do
        table.insert(columns, col)
    end

    local hatList = {}
    for hat in string.gmatch(list, '([^,]+)') do
        table.insert(hatList, hat)
    end

    local tbl = tableBuilder.create({ ['class'] = 'wikitable sortable', ['style'] = 'width:100%;' })

    local colHeaderMap = {
    	["icon"] = "Icon",
        ["name"] = "Hat",
        ["description"] = "Description",
        ["rarity"] = "Rarity",
        ["season"] = "Season",
        ["buy"] = "Buy",
        ["sell"] = "Sell Price"
    }

    local h = tbl:row()

    for _, col in ipairs(columns) do
        local header = colHeaderMap[col]
        if not header then
            error("Columns includes invalid column name: " .. col)
            return
        end
        if col == "icon" then
        	h:th(colHeaderMap[col]):attr('class', 'unsortable'):attr('style', 'width:48px;padding:0;')
    	else
        	h:th(colHeaderMap[col])
    	end
    end

    local prefix = "Companion "
    local suffix = " (Camp Spirit)"

    local function formatPageLink(page)
        return "[[" .. page .. "|" .. string.sub(page, #prefix, - #suffix) .. "]]"
    end
    
    local lang = mw.getContentLanguage()

    for _, hat in ipairs(hatList) do
        local hatData = formatHatData(string.sub(hat, 0, - (#suffix + 1)), frame)
        if hatData ~= nil then
            local r = tbl:row()
            for _, col in ipairs(columns) do
                local header = colHeaderMap[col]
                if not header then
                    error("Columns includes invalid column name: " .. col)
                    return
                end
                if col == "name" or col == "page" then
                    r:td(formatPageLink(hat))
                elseif col == "description" then
                    r:td(hatData.description)
                elseif col == "rarity" then
                    r:td(hatData.rarity)
                elseif col == "icon" then
                	local icon = mw.title.makeTitle('File', hatData.icon .. ".png")
                	local iconSize = 48
                	if icon.file.exists then
                		r:td(frame:expandTemplate{ title = "image", args = { hatData.icon, iconSize, link = hatData.name .. " (Camp Spirit)"} })
            		else
            		    r:td(frame:expandTemplate{ title = "image", args = { "Unknown Item", iconSize, link = hatData.name .. " (Camp Spirit)"} })	
            		end

                elseif col == "season" then
            		if hatData.season == "All" then
                    	r:td("Any Season")
        			else
                    	r:td(frame:expandTemplate { title = "CSLink", args = { hatData.season or "All" } })
                	end
                elseif col == "buy" then
                    r:td(hatData.buy)
                elseif col == "sell" then
                    r:td(hatData.sell)
                end
            end
        else
            tbl:row():td(string.sub(hat, 0, -(#suffix + 1)))
        end
    end

    return tbl:done() ..
    '<sup style="font-style:italic;"><strong>Editor\'s Note:</strong> This table\'s content is automatically generated. The function that generates the table may be edited at [[Module:Camp Spirit/Companion/Hat]].</sup>'
end

function p.GetPage(frame)
    local args = arguments.getArgs(frame)
    local hatData = {
        name = args["name"]
    }
    for k, v in pairs(data.hats[args["name"]]) do
        hatData[k] = v
    end

    return frame:expandTemplate { title = "Companion Clothing Page/Camp Spirit/Content", args = hatData }
end

return p