Module:ItemList

--[=[ This module creates a list of Items belonging to a certain equip-able Class (e.g. "Hat"), and with specific effects (e.g. "Persuasive").

Exposed functions: empty string if no such items exist. Class which have the Quality effect.
 * list_size(Class=class, Quality=quality): returns the size of the expected list, or an
 * create_list(Class=class, Quality=quality): returns the formated list of all Items of the

Modules are not called directly from Wiki pages, but are invoked indirectly via a Template. In this case, the Module is involed via Template:ItemList.

The Module creates the desired list by parsing existing tables of the available equip-able item classes:
 * Module:ItemList/Hats
 * Module:ItemList/Clothing
 * Module:ItemList/Gloves
 * Module:ItemList/Boots
 * Module:ItemList/Companions
 * Module:ItemList/Destinies
 * Module:ItemList/Transport
 * Module:ItemList/Affiliations
 * Module:ItemList/Home Comforts
 * Module:ItemList/Ships
 * Module:ItemList/Spouses
 * Module:ItemList/Clubs

If you want to add a new item to the list generated by this module (or to update an existing one), you need to edit the appropriate table file for the item's class.

Each item class table (e.g. "Hats") contains a list of items, each in the format: ["item name"] = { }, Each is itself a table, which can hold two types of key/value pairs:
 * effects = { }
 * qualifiers = { }

* Both of the above are optional, e.g. if an item has no effects and no qualifiers. * The order of items is irrelevant for the module, but try to list them alphabetically, so help avoid confusion by future editors.

If the item has effects, the should list them all, with its own key/value pairs. For example:
 * effects = { ["Persuasive"] = 2, ["Scandal"] = -1 }

Notes: * The qualities' names are case-sensitive. * Positive numbers should be added without the "+" sign (that will be added automatically     when the list is formatted.). * The order of effects is irrelevant. For each item, the slected quality will be printed first, following by all the otehrs, sorted according to their effect level (menaces last).

If the item has qualifiers, the should list them as a flast list. For example:
 * qualifiers = { "Rose", "Retired", "Fate" }

Notes: * Here, the qualifiers' names are *not* case-sensitive. * "Fate" and "Retired" will always be printed last. The rest of the items will be printed in the order they are listed. * If the qualifier is not of a value known by the module, it will be printed as-is. * Known qualifiers will be printed with special formatting. The list is below, under "local display_qualifier". The values basically include all Profession names, all Ambition names, all Faction names, all Seasonal event names, all Kickstarter reward names, as well as "Fate", "Retired", "Mood", "Protege", "Connected Pet", "Knife & Candle", SMEN", and "Mysteries". --]=] local Hat = mw.loadData('Module:ItemList/Hats') local Clothing = mw.loadData('Module:ItemList/Clothing') local Gloves = mw.loadData('Module:ItemList/Gloves') local Weapon = mw.loadData('Module:ItemList/Weapons') local Boots = mw.loadData('Module:ItemList/Boots') local Companion = mw.loadData('Module:ItemList/Companions') local Destiny = mw.loadData('Module:ItemList/Destinies') local Affiliation = mw.loadData('Module:ItemList/Affiliations') local Transport = mw.loadData('Module:ItemList/Transport') local HomeComfort = mw.loadData('Module:ItemList/Home Comforts') local Ship = mw.loadData('Module:ItemList/Ships') local Spouse = mw.loadData('Module:ItemList/Spouses') local Club = mw.loadData('Module:ItemList/Clubs')

local item_class = { ["Hat"] = Hat, ["Clothing"] = Clothing, ["Gloves"] = Gloves, ["Weapon"] = Weapon, ["Boots"] = Boots, ["Companion"] = Companion, ["Destiny"] = Destiny, ["Affiliation"] = Affiliation, ["Transport"] = Transport, ["Home Comfort"] = HomeComfort, ["Ship"] = Ship, ["Spouse"] = Spouse, ["Club"] = Club, }

local negative_q = { ["Nightmares"] = true, ["Scandal"] = true, ["Suspicion"] = true, ["Wounds"] = true, ["Plagued by a Popular Song"] = true, ["Unaccountably Peckish"] = true, ["Plagued by a Popular Song"] = true }

-- Detect and deal with negative qualities (i.e. menaces). For the purpose of sorting, flip the value's sign local function deal_with_negative_q(quality, value) if (negative_q[quality]) then value = value * (-1) end

return value end

-- This function formats an effect's value for display. For positive numbers, '+' is added up front local function qvalue(v) local str = "" if (v > 0) then str = "+" end return str .. v end

--[[ This function is used to compare two effects to determine which should be displayed first.

Each input effect (a or b) is a table with: q = quality name (e.g. "Persuasive") v = the quality's value (e.g. 2) If the two effects have the same value, then: - Menaces will appear after other qualities. - If both are menaces (or both are not), sort lexicographically by effect name. ]] local function compare_effects(a, b)   if (a.v == b.v) then if (negative_q[a.q] and not negative_q[b.q]) then return false elseif (negative_q[b.q] and not negative_q[a.q]) then return true end return (a.q < b.q)   else return (a.v > b.v)   end end

local special_fonts = { ["Fate"] = "FontFate", ["Retired"] = "FontRetired", ["Rose"] = "FontRose", ["Christmas"] = "FontChristmas", ["Hallowmas"] = "FontHallowmas", ["Fruits"] = "FontFruits", ["Election"] = "FontElection", ["Sunless Sea"] = "FontSunless", ["Sunless Skies"] = "FontSkies", ["Silver Tree"] = "FontSilver", ["Mysteries"] = "FontMysteries", ["Smen"] = "FontSMEN" }

local function special_font(id) local frame = mw.getCurrentFrame if (special_fonts[id] == nil) then return id .. ""   end --return special_fonts[id] return frame:expandTemplate{ title = special_fonts[id] } end

local function display_mood return "Mood" end

local function display_pet return "Connected Pet" end

local function display_kc return "Knife & Candle" end

local function display_ambition(ambition) return ("Ambition: " .. ambition .. " Item") end

local function display_profession(profession) return ("Profession: " .. profession .. " Item") end

local function display_protege return ("The Protégé of a Mysterious Benefactor Item") end

local function display_faction(faction) return ("" .. faction .. " Faction Item") end

-- A hash table for the proper function(s) which know how to create the display text for each possible qualifier. local display_qualifier = { ["Fate"] = special_font, ["Retired"] = special_font, ["Rose"] = special_font, ["Christmas"] = special_font, ["Hallowmas"] = special_font, ["Fruits"] = special_font, ["Election"] = special_font, ["Mood"] = display_mood, ["Connected Pet"] = display_pet, ["Knife & Candle"] = display_kc, ["K & C"] = display_kc, ["K&C"] = display_kc, ["Sunless Sea"] = special_font, ["Sunless Skies"] = special_font, ["Silver Tree"] = special_font, ["Mysteries"] = special_font, ["Smen"] = special_font, ["Nemesis"] = display_ambition, ["Bag a Legend!"] = display_ambition, ["Heart's Desire!"] = display_ambition, ["Light Fingers!"] = display_ambition, ["Campaigner"] = display_profession, ["Mystic"] = display_profession, ["Glassman"] = display_profession, ["Enforcer"] = display_profession, ["Murderer"] = display_profession, ["Licentiate"] = display_profession, ["Journalist"] = display_profession, ["Author"] = display_profession, ["Correspondent"] = display_profession, ["Rat-Catcher"] = display_profession, ["Stalker"] = display_profession, ["Monster-Hunter"] = display_profession, ["Trickster"] = display_profession, ["Conjurer"] = display_profession, ["Crooked-Cross"] = display_profession, ["Watcher"] = display_profession, ["Agent"] = display_profession, ["Midnighter"] = display_profession, ["Protege"] = display_protege, ["Bohemians"] = display_faction, ["Constables"] = display_faction, ["Criminals"] = display_faction, ["Hell"] = display_faction, ["Revolutionaries"] = display_faction, ["Rubbery Men"] = display_faction, ["Society"] = display_faction, ["The Church"] = display_faction, ["The Docks"] = display_faction, ["The Great Game"] = display_faction, ["Tomb-Colonies"] = display_faction, ["Urchins"] = display_faction, }

-- This function formats an item's name for display local function display_item_name(name, fancy) local frame = mw.getCurrentFrame local align = fancy and "left" or "" -- If the item's name ends with "(something)" in parenthesis, strip them -- for the Appearance param of the IL template. local appear = string.gsub(name, "%s*%(.*%)", "") if (appear == name) then appear = nil end

if (appear) then return "* " .. frame:expandTemplate{ title = "IL", args = { name, Size="40px", Alignment=align, Appearance=appear } } else return "* " .. frame:expandTemplate{ title = "IL", args = { name, Size="40px", Alignment=align } } end end

-- Transform a qualifier to its canonical case-sensitive form (for lookup in tables). 1. Set all letter to lowercase. 2. Toggle first letter to uppercase. 3. Toggle letters following a space (e.g. "The Docks") or a '-' (e.g. "Rat-Catcher") to uppercase 4. Toggle a single " A " back to lowercase (for "Bag a Legend!") local function canonical_q_name(qualifier) qualifier = string.lower(qualifier) qualifier = string.gsub(qualifier, "^%l", string.upper) qualifier = string.gsub(qualifier, "[%s-]%l", string.upper) qualifier = string.gsub(qualifier, " A ", string.lower)

return qualifier end

-- Concatenate a new item to the list string. If this is not the first item, add a separator (" " by default) in front of it. local function add_separator(list, new, is_first) local separator = " " if (is_first) then is_first = false end

list = list .. separator .. new

return list, is_first end

--[[ Format for display the list of optional qualifiers which are to be added to the item line, after all the effects.

The optional "FATE", followed by the optional "RETIRED" are to be shown last. ]] local function display_qualifiers(qualifiers) local qualifier_list = "" local is_fate = false local is_retired = false local is_first = true for _, vv in ipairs(qualifiers) do       local c_vv = canonical_q_name(vv) if (c_vv == "Fate") then is_fate = true elseif (c_vv == "Retired") then is_retired = true; else local new_item = display_qualifier[c_vv] and display_qualifier[c_vv](c_vv) or vv           qualifier_list, is_first = add_separator(qualifier_list, new_item, is_first) end end

if (is_fate) then local new_item = special_font("Fate") qualifier_list, is_first = add_separator(qualifier_list, new_item, is_first) end

if (is_retired) then local new_item = special_font("Retired") qualifier_list, is_first = add_separator(qualifier_list, new_item, is_first) end

return qualifier_list end

--[[ This function is used to create the display line for a given item, with the list of its effects, and optional qualifiers at the end.

Input parameters: @name = the item's name @item = the table holding the item's qualities (effects and qualifiers) @quality = the quality to place first in the effects list

Example created line: "Lemurian's Mask (Bizarre +1) FEAST OF THE ROSE

The function returns two elements: @item_line = the generated line of item effect list and qualifiers @sorted = a sorted array holding all of the items effects, with the specified quality (if provided) at the top. Each element of this sorted array is itself a pair of the format: { q="quality", v=value) } ]] local function create_item_line(name, item, quality, fancy)   local effect_value = item.effects[quality]    local item_line = display_item_name(name, fancy) .. " (" .. quality .. " " .. qvalue(effect_value)

local sorted = {} -- Ensure that the selected quality stays at front of effects list -- after the upcoming sort table.insert(sorted, {q=quality, v=(effect_value + 1000)}) for i, e in pairs(item.effects) do       i = canonical_q_name(i) if (i ~= quality) then e = deal_with_negative_q(i, e)           table.insert(sorted, {q=i, v=e}) end end table.sort(sorted, compare_effects)

-- Now that the effects list is sorted, remove the artificial bump -- in value for the selected quality. -- This will allow proper sort *between* item lines later sorted[1].v = sorted[1].v - 1000 for _, vv in ipairs(sorted) do       if (vv.q ~= quality) then local value = deal_with_negative_q(vv.q, vv.v)           item_line = item_line .. ", " .. vv.q .. " " .. qvalue(value) end end item_line = item_line .. ")"   if (item.qualifiers) then        if (fancy) then            item_line = item_line .. " "        end        item_line = item_line .. " " .. display_qualifiers(item.qualifiers)    end    if (fancy) then        item_line = item_line .. " "    end    return item_line, sorted end

-- This function is used to compare two item lines, for the purpose of sorting. Each input item (a or b) is a table with:   l = (the item line, e.g. "Mask of the Rose (Persuasive +1)")    k = sorting key, which is itself a sorted array of item effects.        Each array element of 'k' is a pair: { q="quality", v=value) } The function compares each of the input items' sorting key array elements, one by one. So, for example, if one item's highest quality is +10, and the other's highest quality is +9, the first item will be sorted first. If both have the same level for their highest quality, the second highest quality is compared, and so on.  local function compare_lines(a, b)    -- Go over item a's sorting key's effects one by one, and compare each    -- to b's corresponding sorting key's effect    for i = 1, #a.k do        if (i > #b.k) then            -- No more effectes listed for item b.            -- Just check if what's left for 'a' is positive or negative. if (a.k[i].v > 0) then return true else return false end end if (a.k[i].v > b.k[i].v) then return true elseif (a.k[i].v < b.k[i].v) then return false end end

-- We've checked all of a's listed effects. If there are more effects listed -- for item 'b', then we'll just check if they are positive or negative. if (#b.k > #a.k) then if (b.k[#a.k + 1].v > 0) then return false else return true end end -- Both of the items' effect levels are equivallent. -- Sort lexicographically by item name. return a.l < b.l end

local function create_list(class, quality, fancy) quality = canonical_q_name(quality) local sorted_lines = {} for i, v in pairs(class) do       if (v.effects and v.effects[quality]) then local line, sort_key = create_item_line(i, v, quality, fancy) table.insert(sorted_lines, {l=line, k=sort_key}) end end

table.sort(sorted_lines, compare_lines)

local full_list = "" for _, vv in ipairs(sorted_lines) do       --print (vv.l)        full_list = full_list .. vv.l .. "\n" end

return full_list end

local p = {} function p.create_list(frame) local full_list = "" local class = frame.args[1] or frame:getParent.args[1] local quality = frame.args[2] or frame:getParent.args[2] local fancy = frame.args.fancy or frame:getParent.args.fancy

if (class == "") then class = nil end if (quality == "") then quality = nil end if (fancy == "") then fancy = nil end if (class and quality and item_class[class]) then full_list = create_list(item_class[class], quality, fancy) else full_list = "" end

return full_list end

function p.list_size(frame) local class = frame.args[1] or frame:getParent.args[1] local quality = frame.args[2] or frame:getParent.args[2] if (class == "") then class = nil end if (quality == "") then quality = nil end

local count = 0 if (class and quality and item_class[class]) then for i, v in pairs(item_class[class]) do           if (v.effects and v.effects[quality]) then count = count + 1 end end end

if (count > 0) then return count else return "" end end

return p