Module:Test

--[=[ 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: If name is specified, return that name if such an item exist...       ... with the specified class and/or quality, ... or at all (if neither class nor quality were specified). If no name was specified...       ... returns *any* item name which meet the classs/quality specified. If only class was specified (no name, no quality) return the class name if it exists. If no match, return an empty string. Example usage:
 * items_exist(class, quality, name): check if at least one such item exists in the tables.

specified class and quality effect. If the specified quality is "Other", then the list is made of   all items in the class which have no "regular" effects (main attributes/menaces/BDR). Example usage:
 * create_list(class, quality): returns the formated and sorted list of all items with the

Obsolete functions: class table. Otherwise, it adds the page to a maintenance category. empty string if no such items exist.
 * verify_item(class, item name): return an empty string if such an item exists in the specified
 * list_size(class, quality): returns the size of the expected list, or an

Modules are not called directly from Wiki pages, but are invoked indirectly via a Template. In this case, the Module is invoked 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/Weapons
 * 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 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, each with its own key/value pairs. For example:
 * effects = { ["Persuasive"] = 2, ["Scandal"] = -1 }

Notes: * Quality 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 selected quality will be printed first, followed by all the others, sorted according to their effect level (menaces last).

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

Notes: * Here, Qualifier 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 menace = { ["Nightmares"] = true, ["Scandal"] = true, ["Suspicion"] = true, ["Wounds"] = true, ["Plagued by a Popular Song"] = true, ["Unaccountably Peckish"] = true }

local function flip_sign(value) return value * (-1) end

-- Detect and deal with negative qualities (i.e. menaces). For the purpose of sorting, flip the value's sign local function deal_with_menace(quality, value) return menace[quality] and flip_sign(value) or value end

local regular_q = { ["Watchful"] = true, ["Shadowy"] = true, ["Persuasive"] = true, ["Dangerous"] = true, ["Nightmares"] = true, ["Scandal"] = true, ["Suspicion"] = true, ["Wounds"] = true, ["Bizarre"] = true, ["Dreaded"] = true, ["Respectable"] = true }

--[[ Check whether an item's effects include *any* regular qualities. Returns 'true' only if no regular effects are listed for the item.

"Regular" qualities unclude main attributes, main menaces, and BDR. ]] local function no_regular_qualities(effects) if (effects) then for e_name, e_value in pairs(effects) do           if (regular_q[e_name]) then return false; end end end

return true 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 (menace[a.q] and not menace[b.q]) then return false elseif (menace[b.q] and not menace[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 And Candle"] = display_kc, ["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!") 4. Toggle a single " By " back to lowercase (for "Plagued by a Popular Song") 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) qualifier = string.gsub(qualifier, " By ", string.lower)

return qualifier 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 = qualifier_list .. " " .. new_item end end

if (is_fate) then local new_item = special_font("Fate") qualifier_list = qualifier_list .. " " .. new_item end

if (is_retired) then local new_item = special_font("Retired") qualifier_list = qualifier_list .. " " .. new_item 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 @fancy: use fancy format style @neg: specified quality must have a negative value

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, neg)   local sorted = {}

-- first, create a sorted list of the item's effects if (item.effects) then if (item.effects[quality]) then local value = item.effects[quality] if (neg) then value = flip_sign(value) end

-- Ensure that the selected quality stays at front of effects list -- after the upcoming sort value = value + 1000 table.insert(sorted, {q=quality, v=value}) end

for i, e in pairs(item.effects) do           i = canonical_q_name(i) if (i ~= quality) then e = deal_with_menace(i, e)               table.insert(sorted, {q=i, v=e}) end end table.sort(sorted, compare_effects)

if (item.effects[quality]) then -- 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 end end

-- now start creating the item text line itself local item_line = display_item_name(name, fancy) if (item.effects) then local is_first = true item_line = item_line .. " ("       if (item.effects[quality]) then            -- if a chosen quality was specified, start with it            item_line = item_line .. quality .. " " .. qvalue(item.effects[quality])            is_first = false        end

for _, vv in ipairs(sorted) do           if (vv.q ~= quality) then local value = deal_with_menace(vv.q, vv.v)

if (is_first) then is_first = false else item_line = item_line .. ", "               end item_line = item_line .. vv.q .. " " .. qvalue(value) end end item_line = item_line .. ")"   end    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:   n = the item's name    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.n < b.n end

--[[ Return 'true' if an effects table has the desired quality, or 'false' otherwise.

Parameters: @effects: and item's effects table @quality: quality to look for among the effects (e.g. "Watchful") @neg: specified quality must have a negative value ]] local function quality_match(effects, quality, neg) if (quality == "Other") then return no_regular_qualities(effects) else return (effects and effects[quality] and ((not neg) or effects[quality] < 0)) end end

local function create_list(class, quality, fancy, neg) quality = canonical_q_name(quality) local sorted_lines = {} for i, v in pairs(class) do       if (quality_match(v.effects, quality, neg)) then local line, sort_key = create_item_line(i, v, quality, fancy, neg) table.insert(sorted_lines, {n=i, 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

--[[ Determine whether a specified item has a specific quality in its effects.

Input parameters: @item: specific item table (containing effects and qualifiers) @quality: quality to look for among the effects (e.g. "Watchful") @neg: specified quality must have a negative value

If @item is nil, return false. Otehrewise, if @quality is nil, return true. ]] local function has_quality(item, quality, neg) if (item) then if (quality) then return quality_match(item.effects, quality, neg) else return true end end

return false end

--[[ Find an item in a class table.

Input parameters: @class: item class table (e.g. Hat) @quality: optional name of quality to search (e.g. "Watchful") @name: optional name of item to search @neg: specified quality must have a negative value

If both @quality and @name were specified, return the item's name if it exists in the specified class and its effects include the quality.

If only @name was specified, return the item's name if it exists in the specified class.

If only @quality was specified, return an arbitrary item's name which belongs to the class and has the quality has an effect. ]] local function find_item_in_class(class, quality, name, neg) if (class == nil) then return nil end if (name) then if has_quality(class[name], quality, neg) then return name end elseif (quality) then for name, item in pairs(class) do           if has_quality(item, quality, neg) then return name end end end return nil end

local seasonal = { ["Rose"] = true, ["Christmas"] = true, ["Hallowmas"] = true, ["Fruits"] = true, ["Election"] = true, }

function item_match(item, quality, restrictions, value) if ((item.effects == nil) or (item.effects[quality] == nil)) then return false end

if (value and (item.effects[quality] ~= value)) then return false end

if (restrictions and item.qualifiers) then --print("got restrictions and qualifiers") local qualifier_table = {} for _, q in pairs(item.qualifiers) do     --print("setting qualifier ".. q .. " to 'true'") if (seasonal[q]) then qualifier_table["Seasonal"] = true elseif (display_qualifier[q] == display_profession) then qualifier_table["Profession"] = true elseif (display_qualifier[q] == display_faction) then qualifier_table["Faction"] = true else qualifier_table[q] = true end end

--print("qualifier_table:") for _, q in pairs(qualifier_table) do     --print(_, q)    end for k, v in pairs(restrictions) do     --print("checking restriction " .. k)      if (qualifier_table[k]) then --print("Item failed filter due to " .. k)       return false end end end

return true end

function find_max(class, quality, restrictions) local max = 0 --print("finding max of ".. class) for name, item in pairs(item_class[class]) do   --print("checking item  ".. name) if (item_match(item, quality, restrictions)) then --if (item.effects and item.effects[quality] and item.effects[quality] > max) then if (item.effects[quality] > max) then max = item.effects[quality] end end end --print("Max of " .. class .. " is " .. max) return max end

function find_items(class, quality, restrictions, value) local found = {} local count = 0 for name, item in pairs(item_class[class]) do   --print("Checking match of item " .. name) if (item_match(item, quality, restrictions, value)) then --if (item.effects and item.effects[quality] and item.effects[quality] == value) then found[name] = item count = count + 1 end end

return found, count end

function add_class_to_table(class, quality, restrictions) local sect = "" local max = find_max(class, quality, restrictions) local items, count = find_items(class, quality, restrictions, max)

if (count > 1) then sect = sect .. "| rowspan=\"".. count .."\" " end sect = sect .. "| ".. class .."\n" if (count > 1) then sect = sect .. "| rowspan=\"".. count .."\" " end sect = sect .. "|" if (max > 0) then sect = sect .. " +" .. max end sect = sect .. "\n"

if (count == 0) then sect = sect .. "|\n" sect = sect .. "|\n" else for name, item in pairs(items) do     sect = sect .. "| " .. name .. "\n" sect = sect .. "|"     if (item.qualifiers) then sect = sect .. display_qualifiers(item.qualifiers) end sect = sect .. "\n" sect = sect .. "|-\n" end end

return sect, max end

function create_table(quality, restrictions) local total = 0 local t = "{| class=\"article-table\" border=\"0\" cellspacing=\"1\" cellpadding=\"1\"\n" t = t .. "|-\n" t = t .. "! scope=\"col\" |Slot\n" t = t .. "! scope=\"col\" |Bonus\n" t = t .. "! scope=\"col\" |Item\n" t = t .. "! scope=\"col\" |Notes\n" t = t .. "|-\n"

local class_list = {"Affiliations", "Transport", "Home Comfort"} for i, class in ipairs(class_list) do   local sect, max = add_class_to_table(class, quality, restrictions) total = total + max t = t .. sect end

t = t:sub(1, -2) -- remove last "\n" t = t .. "style=\"border-top:3px solid grey;\"\n" t = t .. "| Total\n" t = t .. "| +" .. total.. "\n" t = t .. "| \n" t = t .. "| \n"

t = t .. "|}" return t end

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

if (class == "") then class = nil end if (quality == "") then quality = nil end if (fancy == "") then fancy = nil end if (neg == "") then neg = nil end if (class and quality and item_class[class]) then full_list = create_list(item_class[class], quality, fancy, neg) else return "" 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 (quality == "Other") then if (no_regular_qualities(v.effects)) then count = count + 1 end else if (v.effects and v.effects[quality]) then count = count + 1 end end end end

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

function p.items_exist(frame) local class = frame.args.class or frame:getParent.args.class local name = frame.args.name or frame:getParent.args.name local quality = frame.args.quality or frame:getParent.args.quality local neg = frame.args.neg or frame:getParent.args.neg if (class == "") then class = nil end if (name == "") then name = nil end if (quality == "") then quality = nil end if (neg == "") then neg = nil end local found = nil if (class) then local c = item_class[class] if (not quality and not name) then found = c and class or nil else found = find_item_in_class(c, quality, name, neg) end else -- no class specified for _, class in pairs(item_class) do           found = find_item_in_class(class, quality, name, neg) if (found) then break end end end

return found or "" end

function p.verify_item(frame) local class = frame.args.class or frame:getParent.args.class local name = frame.args.name or frame:getParent.args.name if (class == "") then class = nil end if (name == "") then name = nil end

local count = 0 if (name and class) then if (item_class[class]) then if (item_class[class][name]) then return "" -- item found end else return "" -- nonexistent class end else return "" -- empty input, silently ignore end

return "" end

function p.best_in_slot(frame) local quality = frame.args.quality or frame:getParent.args.quality return create_table(quality) end

return p