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"). The qualities can be a comma-separated list (without spaces, e.g. "Bizarre,Dreaded,Respectable"), in which case their effects are combined.
Exposed functions[edit]
- items_exist(class, quality, neg): check if at least one such item exists on the wiki.
Returns the number of items that belong to the given class and modify the given quality. If no match, return an empty string. Both class and quality are optional, although at least one should be given. If either are omitted, then items are filtered only for their class or for the quality they modify. By default items are counted if they either increase or decrease the given quality, but if neg is truthy, then only items with negative modifiers are considered. Example usage: {{#invoke:ItemList|items_exist|name=Overgoat|class=Companion}} {{#invoke:ItemList|items_exist|class=Hat|quality=Scandal}} {{#invoke:ItemList|items_exist|quality=Watchful}}
- create_list(class, quality, fancy, neg): returns the formated and sorted list of all items with the 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). If fancy is true, then the result is a table instead of a list, with fancier formatting. If neg is true, then only items with negative stats will be returned, sorted with the most negative stats first. Example usage: {{#invoke:ItemList|create_list|class=Hat|quality=Persuasive}} {{#invoke:ItemList|create_list|class=Companion|quality=Other}}
- best_in_slot(Quality): returns a best-in-slot items table for the specified quality.
The following optional parameters can be set to "no" to exclude from the results items with certain properties: "Fate", "Mood", "Seasonal", "Profession", "Faction", "Ambition," "SMEN", "Retired". Example usage: {{#invoke:ItemList|best_in_slot|Quality=Persuasive}} {{#invoke:ItemList|best_in_slot|Quality=Dangerous|Fate=no|Retired=no}}
- find_minmax(class, quality, minmax, name): Returns information about the best-in-slot bonus for a given class and quality.
The minmax parameter must be either "max" or "min," and directs whether to return the best-in-slot or worst-in-slot values. The name parameter is the name of the item being compared against, which will be excluded from the returned results. The function returns a table with the following keys: * value = max (or min) effect value possible for the specified quality in this item class * count = number of items found with this value * no_mood = max (or min) value possible when disregarding Moods * no_mood_count = number of non-Mood items found with this value * no_fate = max (or min) value possible when disregarding both Fate and Mood items * no_fate_count = number of non-Fate non-Mood items found with this value
Using the Module[edit]
Modules are not called directly from Wiki pages, but are invoked indirectly via a Template. In this case, the Module is invoked via {{ItemList}}.
The Module creates the desired list by parsing arguments passed to the {{Item}} on the pages for individual available equip-able items.
Module table structure[edit]
Lists of items returned by this Module contains a list of items, each in the format:
["item name"] = { <item info> },
Each <item info> is itself a table, which can hold two types of key/value pairs:
- effects = { <effects table> }
- qualifiers = { <qualifiers list> }
* Both of the above are optional, e.g. if an item has no effects and no qualifiers. * The order of items, effects, and qualifiers is not guaranteed.
If the item has effects, the <effects table> will list them all, each with its own
key/value pairs.
For example:
- effects = { ["Persuasive"] = 2, ["Scandal"] = -1 }
Notes:
* Quality names are case-sensitive. * 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 <qualifiers list> will list them as a flat list. For example:
- qualifiers = { "Rose", "Retired", "Fate" }
Notes:
* Qualifier names are also case-sensitive. * "Fate" and "Retired" will always be printed last. * 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 p = {}
local menace = {
["Nightmares"] = true,
["Scandal"] = true,
["Suspicion"] = true,
["Wounds"] = true,
["Plagued by a Popular Song"] = true,
["Troubled Waters"] = true,
["Hearts' Game: Counterplay"] = true
}
local menace_msg = {
[-4] = "Massively reduces",
[-2] = "Greatly reduces",
[-1] = "Reduces",
[1] = "Increases",
[2] = "Greatly increases"
}
local function flip_sign(value)
return value*(-1)
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:
qual = 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.val == b.val) then
if (menace[a.qual] and not menace[b.qual]) then
return false
elseif (menace[b.qual] and not menace[a.qual]) then
return true
end
return (a.qual < b.qual)
else
return (a.val > b.val)
end
end
local special_fonts = {
["Fate"] = "FontFate",
["Exceptional Friendship"] = "FontExceptional",
["Retired"] = "FontRetired",
["Rose"] = "FontRose",
["Christmas"] = "FontChristmas",
["Hallowmas"] = "FontHallowmas",
["Whitsun"] = "FontWhitsun",
["Fruits"] = "FontFruits",
["Election"] = "FontElection",
["Grand Clearing-Out"] = "FontClearing",
["Prelapsarian Exhibition"] = "FontExhibition",
["Horticultural Show"] = "FontHorticultural",
["Sixth Coil"] = "FontSixthCoil",
["Summer festival"] = "FontSummer",
["World Quality"] = "FontWorld",
["Sunless Sea"] = "FontSunless",
["Sunless Skies"] = "FontSkies",
["Mask of the Rose"] = "FontMask",
["Silver Tree"] = "FontSilver",
["Mysteries"] = "FontMysteries",
["SMEN"] = "FontSMEN"
}
local function special_font(id)
local frame = mw.getCurrentFrame()
if (special_fonts[id] == nil or frame == nil) then
return id .. "[[Category:" .. "Module ItemList Internal Errors]]"
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-and-Candle (Guide)|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 .. "|" .. faction .. "]] [[Factions (Guide)#Renown Items|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,
["Exceptional Friendship"] = special_font,
["Retired"] = special_font,
["Rose"] = special_font,
["Christmas"] = special_font,
["Hallowmas"] = special_font,
["Whitsun"] = special_font,
["Fruits"] = special_font,
["Election"] = special_font,
["Grand Clearing-Out"] = special_font,
["Prelapsarian Exhibition"] = special_font,
["Horticultural Show"] = special_font,
["Sixth Coil"] = special_font,
["Summer festival"] = special_font,
["World Quality"] = special_font,
["Mood"] = display_mood,
["Connected Pet"] = display_pet,
["Knife & Candle"] = display_kc,
["Sunless Sea"] = special_font,
["Sunless Skies"] = special_font,
["Mask of the Rose"] = 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,
["Silverer"] = 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,
}
local il = nil
--[[
This function formats an item's name for display
]]
local function display_item_name(name, fancy, icon)
-- repeated calls to frame:expandTemplate{} was slowing down page due to FFI
-- instead we call {{IL}} once, cache it, and sub out the values
-- item icon is part of item details obtained from SMW
if not il then
local frame = mw.getCurrentFrame()
if (frame == nil) then
return "'''no frame: " .. name .. "'''"
end
-- the <brackets> prevent overlaps with real values, e.g. "icon" appears in "Helicon"
il = frame:expandTemplate{title = "IL", args = {"<item>", Size = "40px", Image = "<icon.png>", Alignment = "<align>", Appearance = "<alias>"}}
end
local link = string.gsub(il, "(<item>)", name)
link = string.gsub(link, "(File:<icon%.png>)", icon)
link = string.gsub(link, "(<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*%(.*%)", "")
link = string.gsub(link, "(<alias>)", appear)
return link
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
if (vv == "Fate") then is_fate = true
elseif (vv == "Retired") then is_retired = true;
else
local new_item = display_qualifier[vv] and display_qualifier[vv](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)
@qualities: the qualities 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 a table with the following members:
@name: the item's name
@line: the generated line of item effect list and qualifiers
@display_value: the value to be displayed in the first column in "fancy"
mode.
@sort_key: a sorted array holding all of the items effects.
The first effect is a synthetic "buff" effect which is the
summed value of the specified qualities (if provided). This
is followed by the specified qualities, then the remainder.
Each element of this sorted array is itself a pair
of the format: { qual="quality", val=value) }
]]
local function create_item_line(name, item, qualities, fancy, neg)
local sorted = {}
local effects = item.effects
if not effects then effects = {} end
local detriments = {}
local benefits = {}
local menacereduce = {}
local menaceincrease = {}
local value = 0
-- first, create a sorted list of the item's effects
for effectquality, effectvalue in pairs(effects) do
local found = false
-- We assume not many qualities, otherwise we'd build a look-up table
for p, desiredquality in ipairs(qualities) do
if effectquality == desiredquality then
found = p
break
end
end
local ismenace = menace[effectquality]
if ismenace then
if effectvalue > 0 then
effectvalue = (effectvalue)
if effectquality then effectvalue = effectvalue end
if found then
-- Use a placeholder value to get correct sort order
effectvalue = effectvalue + 1000
end
table.insert(menaceincrease, {qual=effectquality, val=effectvalue})
end
if effectvalue < 0 then
effectvalue = (effectvalue)
if effectquality then effectvalue = effectvalue end
if found then
-- Use a placeholder value to get correct sort order
effectvalue = effectvalue + 1000
end
table.insert(menacereduce, {qual=effectquality, val=effectvalue})
end
else
if effectvalue > 0 then
effectvalue = (effectvalue)
if effectquality then effectvalue = effectvalue end
if found then
-- Use a placeholder value to get correct sort order
effectvalue = effectvalue + 1000
end
table.insert(benefits, {qual=effectquality,val=effectvalue})
end
if effectvalue < 0 then
if found then
-- Use a placeholder value to get correct sort order
effectvalue = effectvalue + 1000
end
table.insert(detriments, {qual=effectquality, val=effectvalue})
end
end
end
table.insert(sorted, {qual="buff", val=2000})
table.sort(benefits, compare_effects)
table.sort(menacereduce, compare_effects)
table.sort(detriments, compare_effects)
table.sort(menaceincrease, compare_effects)
for k,entry in pairs(benefits) do sorted[#sorted+1] = entry end
for k,entry in pairs(menacereduce) do sorted[#sorted+1] = entry end
for k,entry in pairs(detriments) do sorted[#sorted+1] = entry end
for k,entry in pairs(menaceincrease) do sorted[#sorted+1] = entry end
-- Now that the effects list is sorted, fix the values for the selected
-- qualities
for idx, entry in ipairs(sorted) do
if idx ~= 1 and entry.val > 500 then
value = value + effects[entry.qual]
sorted[idx].val = effects[entry.qual]
end
end
local display_value = value
if #qualities == 1 then
-- This is quite the hack. Menaces get sorted in reverse order, and
-- this is accomplished by negating their values higher up. This means
-- the total value will also be negative, which is good for sorting,
-- but bad for display. *However*, if we're adding multiple qualities
-- together, things get too complicated - what if we're adding menace
-- and non-menace qualities? So we only try to undo the negation in
-- the case where there's only one quality involved.
display_value = (value)
end
-- When we're selecting only negative items, we also want to sort in
-- reverse order, so negate the buff value. But the display value needs to
-- remain untouched.
if (neg) then value = flip_sign(value) end
sorted[1].val = value
-- now start creating the item text line itself
local item_line = display_item_name(name, fancy, item.icon)
if (item.effects) then
item_line = item_line .. " ("
local transformed = {}
for i, entry in ipairs(sorted) do
if i ~= 1 then -- Skip artificial buff line
if menace[entry.qual] then
transformed[i-1] = menace_msg[entry.val] .. ' ' .. entry.qual .. ' ' .. 'build up.'
else
transformed[i-1] = entry.qual .. " " .. qvalue(
(entry.val))
end
end
end
item_line = item_line .. table.concat(transformed, ", ") .. ")"
end
if (item.qualifiers) then
if (fancy) then
item_line = item_line .. "<br/>"
end
item_line = item_line .. display_qualifiers(item.qualifiers)
end
if (fancy) then
item_line = item_line .. "<br clear=all/>"
end
return {name=name, line=item_line, display_value=display_value,
sort_key=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:
name = the item's name
line = the item line (e.g. "Mask of the Rose (Persuasive +1)")
sort_key = sorting key, which is itself a sorted array of item effects.
Each array element of 'k' is a pair: { qual="quality", val=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
and so on. If the lists of effect levels for both items are identical, they
are sorted according to the item's name.
]]
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.sort_key do
if (i > #b.sort_key) then
-- No more effectes listed for item b.
-- Just check if what's left for 'a' is positive or negative.
if (a.sort_key[i].val > 0) then
return true
else
return false
end
end
local first = a.sort_key[i]
local second = b.sort_key[i]
if (first.val > second.val) then
return true
elseif (first.val < second.val) 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.sort_key > #a.sort_key) then
if (b.sort_key[#a.sort_key + 1].val > 0) then
return false
else
return true
end
end
-- Both of the items' effect levels are equivallent.
-- Sort lexicographically by item name.
return a.name < b.name
end
--[[
Adapter function retrieves item data stored in SMW.
Input parameters:
@header: A string containing the Selection part of the SMW query, using Ask syntax
The returned list contains item detail tables as described in the top-level documentation for this module.
]]
local function smw_fetch_items(header)
local extendedResult = mw.smw.getQueryResult(header .. [===[
[[!The Imperturbable Patroness]]
[[:+]]
|?=Item
|?Has icon=Icon
|?Has effect#-=Effects
|?Category:Fate Items=Fate
|?-Has renown item=Faction
|?-Has profession item=Profession
|?Category:Mood
|?Category:SMEN
|?Category:Knife-and-Candle Medals=Knife & Candle
|?Category:Connected Pet
|?Category:Ambition: Light Fingers! Items=Light Fingers!
|?Category:Ambition: Heart's Desire! Items=Heart's Desire!
|?Category:Ambition: Nemesis Items=Nemesis
|?Category:Ambition: Bag a Legend! Items=Bag a Legend!
|?Category:Retired Items=Retired
|?Category:Hallowmas Equipment=Hallowmas
|?Category:Feast of the Rose Equipment=Rose
|?Category:Christmas Equipment=Christmas
|?Category:Election Equipment=Election
|?Category:Grand Clearing-Out Equipment=Grand Clearing-Out
|?Category:Prelapsarian Exhibition Equipment=Prelapsarian Exhibition
|?Category:Horticultural Show Equipment=Horticultural Show
|?Category:Sixth Coil Equipment=Sixth Coil
|?Category:Summer festival Equipment=Summer festival
|?Category:Fruits of the Zee Equipment=Fruits
|?Category:Whitsun Equipment=Whitsun
|?Category:World Quality Equipment=World Quality
|?Category:Sunless Sea Items=Sunless Sea
|?Category:Sunless Skies Items=Sunless Skies
|?Category:Mask of the Rose Items=Mask of the Rose
|?Category:Protégé Items=Protege
|?Category:Fallen London Mysteries=Mysteries
|?Category:Exceptional Friendship Equipment=Exceptional Friendship
|limit=1000
]===]);
local results = {};
for i, item in ipairs(extendedResult.results) do
local name = item.fulltext;
local quals = {};
for q, b in pairs(item.printouts) do
if b[1] == "t" then
table.insert(quals, q);
elseif q == "Faction" and #b == 1 then
table.insert(quals, string.sub(b[1].fulltext, 10)); -- strip "Faction: "
elseif q == "Profession" and #b == 1 then
table.insert(quals, b[1].fulltext);
end
end
local effs = {};
for j, e in ipairs(item.printouts.Effects) do
effs[e["Modifies quality"].item[1].fulltext] = e["Modifies by"].item[1];
end
local icon
if item.printouts.Icon ~= nil then
icon = item.printouts.Icon[1].fulltext
else
icon = 'Question'
end
results[name] = {effects = effs, qualifiers = quals, icon = icon};
end
return results;
end
--[[
Queries the SMW database for a list of the items that can be equipped in a given slot and provide a bonus (or malus) to the given quality
Input parameters:
@class: The name of the equipment slot
@quality: The name of the quality being filtered
The returned list contains item detail tables and described in the top-level documentation for this module.
]]
local function fetch_items(class, quality, neg, other)
local header = "[[Equips in slot::" .. class .. "]]"
header = header .. "[[Has effect::" .. string.gsub(quality,",","||")
if neg then
header = header .. ";<0]]"
else
header = header .. ";?]]"
end
if other then header = header .. other end
return smw_fetch_items(header);
end
local function create_list(class, quality, fancy, neg)
local qualities = mw.text.split(quality, ",", true)
local sorted_lines = {}
for name, item in pairs(fetch_items(class, quality, neg)) do
table.insert(sorted_lines, create_item_line(name, item, qualities, fancy, neg))
end
table.sort(sorted_lines, compare_lines)
if fancy then
local rowspan = {}
local current_buff = 0
local last_i = 0
for i, vv in ipairs(sorted_lines) do
local buff = vv.display_value
if buff ~= current_buff then
if last_i ~= 0 then
rowspan[last_i] = i - last_i
end
last_i = i
current_buff = buff
end
end
rowspan[last_i] = #sorted_lines + 1 - last_i
local result = '{| class="article-table"\n|-\n'
if last_i ~= 0 then -- If we have *any* buff columns
result = result .. '! scope="col" |Value\n'
end
result = result .. '! scope="col" |Item\n'
local out_lines = {}
for i, vv in ipairs(sorted_lines) do
local line = "|-\n"
if rowspan[i] then
if menace[quality] then
line = line .. string.format([[| rowspan="%d" |<big>%s</big><br><big>build up.</big>
]], rowspan[i], menace_msg[vv.display_value])
else
line = line .. string.format([[| rowspan="%d" |<big><big>%+d</big></big>
]], rowspan[i], vv.display_value)
end
end
out_lines[i] = line .. "|" .. vv.line .. "\n"
end
return result .. table.concat(out_lines) .. "|}\n"
else
local out_lines = {}
for i, vv in ipairs(sorted_lines) do
out_lines[i] = "* " .. vv.line .. "\n"
end
return table.concat(out_lines)
end
end
local seasonal = {
["Rose"] = true,
["Christmas"] = true,
["Hallowmas"] = true,
["Fruits"] = true,
["Election"] = true,
["Grand Clearing-Out"] = true,
["Prelapsarian Exhibition"] = true,
["Horticultural Show"] = true,
["Sixth Coil"] = true,
["Summer festival"] = true,
["Whitsun"] = true,
}
--[[
Returns the combined bonuses of an item
Input parameters:
@effects: Item effects table of the form "quality" --> value (e.g. "Dreaded" --> 1)
@quality: The quality to search, possibly a comma-separated string
]]
local function sum_value(effects, quality)
if (effects == nil) then
return 0
end
local value = 0
for q in mw.text.gsplit(quality, ",", true) do
if (effects[q]) then
value = value + effects[q]
end
end
return value
end
--[[
Check if an item had the desired quality
Input parameters:
@name: item name
@item: item structure (containing optional "effects" and "qualifiers" sub-tables)
@quality: the quality to search (e.g. "Watchful")
@restrictions: option restrictions of which items not to include
]]
local function item_match(item, restrictions)
if (restrictions and item.qualifiers) then
-- scan item's qualifiers list and create a table which can be easily compared
-- to the restrictions table provided.
local qualifier_table = {}
for _, q in pairs(item.qualifiers) do
if (seasonal[q]) then
qualifier_table["Seasonal"] = true
elseif (display_qualifier[q] == display_profession) then
qualifier_table["Profession"] = q
elseif (display_qualifier[q] == display_faction) then
qualifier_table["Faction"] = true
elseif (display_qualifier[q] == display_ambition) then
qualifier_table["Ambition"] = q
else
qualifier_table[q] = true
end
end
for k, v in pairs(restrictions) do
if (qualifier_table[k]) then
if v == true or qualifier_table[k] ~= v then
return false
end
end
end
end
return true
end
--[[
Check if a list of items are all profession items
Input parameters:
@items: a table ("name" --> item structure (with its own effects and qualifiers))
For each item provided, scan all its qualifiers to see if it's a profession item.
Return 'true' iff all items are profession items.
]]
local function all_professions(items)
local item_count = 0
local profession_count = 0
for name, item in pairs(items) do
item_count = item_count + 1
if (item.qualifiers) then
for __, q in ipairs(item.qualifiers) do
if (display_qualifier[q] == display_profession) then
profession_count = profession_count +1
end
end
end
end
if (item_count == 0) then
return true
end
return (item_count == profession_count)
end
local function create_entry(found, value)
local entry = {}
entry.value = value
entry.count = found[value].count
entry.items = found[value].items
return entry
end
local function find_items(class, quality, restrictions)
local found = {}
local count = 0
local none_found = true
for name, item in pairs(fetch_items(class, quality)) do
if (item_match(item, restrictions)) then
none_found = false
local value = sum_value(item.effects, quality)
if (found[value] == nil) then
found[value] = {}
found[value].count = 0
found[value].items = {}
end
found[value].items[name] = item
found[value].count = found[value].count + 1
end
end
if (none_found) then
return found
end
sorted = {}
for value, items in pairs(found) do
table.insert(sorted, value)
end
table.sort(sorted, function(a,b) return (a > b) end)
local top = sorted[1]
-- If the best item found has no positive impact on the quality we don't want it here
if (top <= 0) then
return {}
end
local relevant = {}
table.insert(relevant, create_entry(found, sorted[1]))
if (all_professions(found[top].items)) then
if (sorted[2]) then
table.insert(relevant, create_entry(found, sorted[2]))
end
end
found = relevant
return found
end
--[[
Create the table rows of a best-in-slot table for a given item class.
Input parameters:
@class: item class (e.g. "Hats", etc.)
@entry: an array wth either one or two objects in it. Each of them is a table with the keys:
"count" = number of items in this object
"value" = the bonus value of the sought after quality
"items" = items table ("item name" --> effects and qualifiers)
The function returns the values:
@section:
@value: The max value of the quality for the given item class (to be used by the calling
function to agregate the total bonus to the uqality from all classes)
@compensate: optional value. If specified, the calling function should add this to the total,
but only once for all classes. This will only occur when there are multiple
options for this and for another class.
]]
local function add_class_to_table(class, entry)
local sect = ""
local count = 0
for i=1,#entry do
count = count + entry[i].count
end
if (count > 1) then
sect = sect .. "| rowspan=\"".. count .."\" "
end
sect = sect .. "| [[".. class .."]]\n"
if (count == 0) then
sect = sect .. "|\n"
sect = sect .. "|\n"
sect = sect .. "|\n"
sect = sect .. "|-\n"
return sect, 0, 0
end
for i=1,#entry do
if (entry[i].count > 1) then
sect = sect .. "| rowspan=\"".. entry[i].count .."\" "
end
sect = sect .. "|"
if (entry[i].value > 0) then
sect = sect .. " +" .. entry[i].value
end
if (#entry > 1 and i == 1) then
sect = sect .. " [*]"
end
sect = sect .. "\n"
for name, item in pairs(entry[i].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
local compensate_for_total = 0
if (#entry > 1) then
compensate_for_total = entry[1].value - entry[2].value
end
return sect, entry[#entry].value, compensate_for_total
end
--[[
Create the quality's best-in-slot wiki table
Input parameters:
@quality: the desired quality (e.g. "Persuasive")
@restrictions: option restrictions on the items to take into acount
The function returns a formatted wiki code table.
]]
function p.create_table(quality, restrictions)
local total = 0
local item_page_list = {
["Watchful"] = true,
["Shadowy"] = true,
["Persuasive"] = true,
["Dangerous"] = true,
["Bizarre"] = true,
["Dreaded"] = true,
["Respectable"] = true,
["A Player of Chess"] = true,
["Artisan of the Red Science"] = true,
["Glasswork"] = true,
["Kataleptic Toxicology"] = true,
["Mithridacy"] = true,
["Monstrous Anatomy"] = true,
["Shapeling Arts"] = true,
["Steward of the Discordance"] = true,
["Neathproofed"] = true,
["Chthonosophy"] = true,
["Insubstantial"] = true,
["Inerrant"] = true,
}
local item_page = "Item"
if (item_page_list[quality]) then
item_page = "[[" .. quality .. " Items|Item]]"
elseif (quality == "BDR") then
item_page = "[[Bizarre, Dreaded, Respectable (Guide)|Item]]"
quality = "Bizarre,Dreaded,Respectable"
end
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_page .. "\n"
t = t .. "! scope=\"col\" |Notes\n"
t = t .. "|-\n"
local class_list = {"Boon", "Hat", "Clothing", "Adornment", "Gloves", "Weapon",
"Boots", "Luggage", "Companion", "Treasure", "Destiny",
"Tools of the Trade", "Affiliation", "Transport",
"Home Comfort", "Ship", "Crew", "Spouse", "Club",
"Airship", "Burden", "Accoutrement"
}
-- populate class table
local class_items = {}
local max_prof_adv = 0
local solo_prof_class = nil
for i, class in ipairs(class_list) do
class_items[class] = find_items(class, quality, restrictions)
if (#class_items[class] > 1) then
local prof_adv = class_items[class][1].value - class_items[class][2].value
if (prof_adv > max_prof_adv) then
max_prof_adv = prof_adv
solo_prof_class = class
elseif (prof_adv == max_prof_adv) then
solo_prof_class = nil
end
end
end
-- for classes with the top items coming from professions,
-- remove the second choices except when the profession item
-- advantage is the max
for i, class in ipairs(class_list) do
if (#class_items[class] > 1) then
local prof_adv = class_items[class][1].value - class_items[class][2].value
if (prof_adv < max_prof_adv) then
class_items[class][1] = class_items[class][2]
class_items[class][2] = nil
end
end
end
-- if only one class has the top profession item, trim its runner ups
if (solo_prof_class) then
class_items[solo_prof_class][2] = nil
end
local compensate_total = 0
for i, class in ipairs(class_list) do
local sect, max_v, compensate = add_class_to_table(class, class_items[class])
if (compensate > 0) then
compensate_total = compensate
end
total = total + max_v
t = t .. sect
end
total = total + compensate_total
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 .. "|}"
if (compensate_total > 0) then
t = t .. " [*] Profession items are mutually exclusive"
end
return t
end
--[[
Get an argument passed to the module.
Input parameters:
@frame: frame object
@name: parameter name
If the parameter was no specified, or if it an empty string, return nil.
]]
local function get_arg(frame, name)
local arg = frame.args[name] or frame:getParent().args[name]
if (arg == "") then arg = nil end
return arg
end
function p.create_list_body(class, quality, fancy, 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) then
full_list = create_list(class, quality, fancy, neg)
else
return "[[Category:" .. "Module ItemList Parameter Errors]]"
end
return full_list
end
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
return p.create_list_body(class, quality, fancy, neg)
end
function p.items_exist_body(class, quality, neg)
if (class == "") then class = nil end
if (quality == "") then quality = nil end
if (name == "") then name = nil end
if (neg == "") then neg = nil end
quality = string.gsub(quality, ",", "||")
local query = {}
if class then table.insert(query, "[[Equips in slot::" .. class .. "]]") end
if quality then
if neg then
table.insert(query, "[[Has effect::" .. quality .. ";<0]]")
else
table.insert(query, "[[Has effect.Modifies quality::" .. quality .. "]]")
end
end
local count = mw.smw.ask(table.concat(query, " "), "format=count")
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 quality = frame.args.quality or frame:getParent().args.quality
local neg = frame.args.neg or frame:getParent().args.neg
return p.items_exist_body(class, quality, neg)
end
function p.best_in_slot(frame)
local quality = get_arg(frame, "Quality")
local possible_restrictions = {
"Fate",
"Mood",
"Seasonal",
"Faction",
"Profession",
"Ambition",
"SMEN",
"Retired",
}
local restrictions = {}
for _, v in ipairs(possible_restrictions) do
local arg = get_arg(frame, v)
if (arg == "no" or arg == "none") then
restrictions[v] = true
elseif (arg ~= "yes" and arg ~= "all") then
restrictions[v] = arg
end
end
if (quality) then
return p.create_table(quality, restrictions)
else
return "[[Category:" .. "Module ItemList Parameter Errors]]"
end
end
--[[
Return the max value of a quality in a given class
Input parameters:
@class: e.g. "Hat"
@quality: e.g. "Watchful". Can also be a comma-separated list of qualities
@mood: true if Mood items should be included, false otherwise
The function returns a table with the following keys:
* value = max effect value possible for the specified quality in this item class
* count = number of items found with this max value
* no_mood = max value possible when disregarding Moods
* no_mood_count = number of non-Mood items found with this max value
* no_fate = max value possible when disregarding both Fate and Mood items
* no_fate_count = number of non-Fate non-Mood items found with this max value
If the parameters were not specified, or an invalid class name, return nil.
Note: All Mood items are already best-in-slot and already non-Fate. Therefore
the "no_fate" value/counter returned are for those non-Fate items which
are also not Moods.
]]
function p.find_minmax(class, quality, minmax, name)
local extreme_values = {}
extreme_values.value = 0
extreme_values.count = 0
extreme_values.no_mood = 0
extreme_values.no_mood_count = 0
extreme_values.no_fate = 0
extreme_values.no_fate_count = 0
-- Exclude self, to avoid returning stale data
for name, item in pairs(fetch_items(class, quality, nil, "[[!" .. name .. "]]")) do
local non_fate_item = true
local non_mood_item = true
local non_retired = true
if (item.qualifiers) then
for _, q in ipairs(item.qualifiers) do
if (q == "Retired") then
non_retired = false
break
end
if (q == "Fate") then
non_fate_item = false
elseif (q == "Mood") then
non_mood_item = false
end
end
end
-- Skip comparison to Retired items
if (non_retired) then
local value = sum_value(item.effects, quality)
if ((minmax == "max" and value > extreme_values.value) or (minmax == "min" and value < extreme_values.value)) then
extreme_values.value = value
extreme_values.count = 1
elseif (value == extreme_values.value) then
extreme_values.count = extreme_values.count + 1
end
if (non_mood_item) then
if ((minmax == "max" and value > extreme_values.no_mood) or (minmax == "min" and value < extreme_values.no_mood)) then
extreme_values.no_mood = value
extreme_values.no_mood_count = 1
elseif (value == extreme_values.no_mood) then
extreme_values.no_mood_count = extreme_values.no_mood_count + 1
end
end
if (non_fate_item and non_mood_item) then
if ((minmax == "max" and value > extreme_values.no_fate) or (minmax == "min" and value < extreme_values.no_fate)) then
extreme_values.no_fate = value
extreme_values.no_fate_count = 1
elseif (value == extreme_values.no_fate) then
extreme_values.no_fate_count = extreme_values.no_fate_count + 1
end
end
end
end
return extreme_values
end
function p.categories_body(name, class)
local cat = ""
local item = nil
if (name == nil) then return "" end
if (class) then
if (item_class[class]) then
item = item_class[class][name]
end
else
for _, class in pairs(item_class) do
item = class[name]
if (item) then
break
end
end
end
if (item == nil) then return "" end
local cat_names = {
["Rose"] = "Feast of the Rose Equipment",
["Christmas"] = "Christmas Equipment",
["Hallowmas"] = "Hallowmas Equipment",
["Whitsun"] = "Whitsun Equipment",
["Fruits"] = "Fruits of the Zee Equipment",
["Election"] = "Election Equipment",
["Grand Clearing-Out"] = "Grand Clearing-Out Equipment",
["Prelapsarian Exhibition"] = "Prelapsarian Exhibition Equipment",
["Horticultural Show"] = "Horticultural Show Equipment",
["Sixth Coil"] = "Sixth Coil Equipment",
["Summer festival"] = "Summer festival Equipment",
["World Quality"] = "World Quality Equipment",
["Fate"] = "Fate Items",
["Exceptional Friendship"] = "Exceptional Friendship Equipment",
["Retired"] = "Retired Items",
["Mysteries"] = "Retired Items",
["[[Impossible!]]"] = "Retired Items",
["Exotica"] = "Retired Items",
}
if (item.qualifiers) then
for _, q in ipairs(item.qualifiers) do
if (cat_names[q]) then
cat = cat .. "[[Category:" .. cat_names[q] .. "]]"
elseif (display_qualifier[q] == display_faction) then
cat = cat .. "[[Category:Renown " .. "Items]]"
cat = cat .. "[[Category:Faction: " .. q .. "]]"
elseif (display_qualifier[q] == display_profession) then
cat = cat .. "[[Category:Profession " .. "Items]]"
cat = cat .. "[[Category:" .. q .. "]]"
end
end
end
if (item.effects) then
for q, _ in pairs(item.effects) do
cat = cat .. "[[Category:" .. q .. " Items]]"
end
end
return cat
end
return p