Module:Adnoam/ItemList
From Fallen London Wiki (Staging)
Documentation for this module may be created at Module:Adnoam/ItemList/doc
local p = {}
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,
["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,
["Zeefaring"] = 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, BDR and the parabolan attributes.
]]
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",
["Whitsun"] = "FontWhitsun",
["Fruits"] = "FontFruits",
["Election"] = "FontElection",
["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 .. "]] [[Renown Items (Guide)|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,
["Whitsun"] = special_font,
["Fruits"] = special_font,
["Election"] = 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,
}
--[[
This function formats an item's name for display
]]
local function display_item_name(name, fancy)
local frame = mw.getCurrentFrame()
if (frame == nil) then
return "'''no frame: " .. name .. "'''"
end
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
--[[
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: { q="quality", v=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 i, e in pairs(effects) do
local found = false
-- We assume not many qualities, otherwise we'd build a look-up table
for p, q in ipairs(qualities) do
if i == q then
found = p
break
end
end
ismenace = menace[i]
if ismenace then
if e > 0 then
e = (e)
if i then e = e end
if found then
-- Use a placeholder value to get correct sort order
e = e + 1000
end
table.insert(menaceincrease, {q=i, v=e})
end
if e < 0 then
e = (e)
if i then e = e end
if found then
-- Use a placeholder value to get correct sort order
e = e + 1000
end
table.insert(menacereduce, {q=i, v=e})
end
else
if e > 0 then
e = (e)
if i then e = e end
if found then
-- Use a placeholder value to get correct sort order
e = e + 1000
end
table.insert(benefits, {q=i,v=e})
end
if e < 0 then
if found then
-- Use a placeholder value to get correct sort order
e = e + 1000
end
table.insert(detriments, {q=i, v=e})
end
end
end
table.insert(sorted, {q="buff", v=2000})
table.sort(benefits, compare_effects)
table.sort(menacereduce, compare_effects)
table.sort(detriments, compare_effects)
table.sort(menaceincrease, compare_effects)
for k,v in pairs(benefits) do sorted[#sorted+1] = v end
for k,v in pairs(menacereduce) do sorted[#sorted+1] = v end
for k,v in pairs(detriments) do sorted[#sorted+1] = v end
for k,v in pairs(menaceincrease) do sorted[#sorted+1] = v end
-- Now that the effects list is sorted, fix the values for the selected
-- qualities
for idx, q in ipairs(sorted) do
if idx ~= 1 and q.v > 500 then
value = value + effects[q.q]
sorted[idx].v = effects[q.q]
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].v = value
-- now start creating the item text line itself
local item_line = display_item_name(name, fancy)
if (item.effects) then
item_line = item_line .. " ("
local transformed = {}
for i, vv in ipairs(sorted) do
if i ~= 1 then -- Skip artificial buff line
transformed[i-1] = vv.q .. " " .. qvalue(
(vv.v))
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
local function create_item_line_previous(name, item, qualities, fancy, neg)
local sorted = {}
local effects = item.effects
if not effects then effects = {} end
-- first, create a sorted list of the item's effects
for i, e in pairs(effects) do
local found = false
-- We assume not many qualities, otherwise we'd build a look-up table
for p, q in ipairs(qualities) do
if i == q then
found = p
break
end
end
e = (e)
if i then e = e end
if found then
-- Use a placeholder value to get correct sort order
e = e + 1000
end
table.insert(sorted, {q=i, v=e})
end
-- Add a "total buff" placeholder which will sort first
table.insert(sorted, {q="buff", v=2000})
table.sort(sorted, compare_effects)
-- Now that the effects list is sorted, fix the values for the selected
-- qualities and the "total buff" placeholder.
local value = 0
for idx, q in ipairs(sorted) do
if idx ~= 1 and q.v > 500 then
value = value + effects[q.q]
sorted[idx].v = effects[q.q]
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].v = value
-- now start creating the item text line itself
local item_line = display_item_name(name, fancy)
if (item.effects) then
item_line = item_line .. " ("
local transformed = {}
for i, vv in ipairs(sorted) do
if i ~= 1 then -- Skip artificial buff line
transformed[i-1] = vv.q .. " " .. qvalue(
(vv.v))
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: { 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
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].v > 0) then
return true
else
return false
end
end
local first = a.sort_key[i]
local second = b.sort_key[i]
if (first.v > second.v) then
return true
elseif (first.v < second.v) then
return false
elseif first.q > second.q then
return true
elseif first.q < second.q 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].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.name < b.name
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, possibly a comma-separated list
@neg: specified quality must have a negative value
]]
local function quality_match(effects, quality, neg)
if (quality == "Other") then
return no_regular_qualities(effects)
end
if (effects == nil) then
return false
end
for q in mw.text.gsplit(quality, ",", true) do
if (effects[q] and ((not neg) or effects[q] < 0)) then
return true
end
end
return false
end
local function create_list(class, quality, fancy, neg)
local qualities = mw.text.split(quality, ",", true)
local sorted_lines = {}
for name, item in pairs(class) do
if (quality_match(item.effects, quality, neg)) then
table.insert(sorted_lines, create_item_line(name, item, qualities, fancy, neg))
end
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
line = line .. string.format([[| rowspan="%d" |<big><big>%+d</big></big>
]], rowspan[i], vv.display_value)
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
--[[
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,
["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(name, item, quality, restrictions)
if (not quality_match(item.effects, quality, false)) then
return false
end
-- special case: ignore. Companion can never be used while in London
if (name == "The Imperturbable Patroness") then
return false
end
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(item_class[class]) do
if (item_match(name, item, quality, 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 returnes 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
}
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 = {"Hat", "Clothing", "Gloves", "Weapon", "Boots", "Companion", "Destiny",
"Affiliation", "Transport", "Home Comfort", "Ship", "Spouse", "Club"}
-- 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 and item_class[class]) then
full_list = create_list(item_class[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.list_size_body(class, quality)
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.list_size(frame)
local class = frame.args[1] or frame:getParent().args[1]
local quality = frame.args[2] or frame:getParent().args[2]
return p.list_size_body(class, quality)
end
function p.items_exist_body(class, quality, name, 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
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.items_exist(frame)
local class = frame.args.class or frame:getParent().args.class
local quality = frame.args.quality or frame:getParent().args.quality
local name = frame.args.name or frame:getParent().args.name
local neg = frame.args.neg or frame:getParent().args.neg
return p.items_exist_body(class, quality, name, neg)
end
function p.verify_item_body(class, 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 "[[Category:" .. "Module ItemList Parameter Errors]]" -- nonexistent class
end
else
return "" -- empty input, silently ignore
end
return "[[Category:" .. "Items without Module ItemList data]]"
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
return p.verify_item_body(class, name)
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: Optional item class. Can be nil or an empty string to search them all.
@name: Item name. Must be specified.
The function returns true if the item is marked as Retired, and flase it it is not.
Returns nil if no item of that class/name can be found.
]]
function p.is_retired(class, name)
if (name == nil) then return nil end
item = nil
if (class and 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 nil end
if (item.qualifiers) then
for _, q in ipairs(item.qualifiers) do
if (q == "Retired") then
return true
end
end
end
return false
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_max(class, quality)
if (class == nil or item_class[class] == nil or quality == nil) then
return nil
end
local max_values = {}
max_values.value = 0
max_values.count = 0
max_values.no_mood = 0
max_values.no_mood_count = 0
max_values.no_fate = 0
max_values.no_fate_count = 0
for name, item in pairs(item_class[class]) do
if (name ~= "The Imperturbable Patroness" and
has_quality(item, quality, false)) then
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 (value > max_values.value) then
max_values.value = value
max_values.count = 1
elseif (value == max_values.value) then
max_values.count = max_values.count + 1
end
if (non_mood_item) then
if (value > max_values.no_mood) then
max_values.no_mood = value
max_values.no_mood_count = 1
elseif (value == max_values.no_mood) then
max_values.no_mood_count = max_values.no_mood_count + 1
end
end
if (non_fate_item and non_mood_item) then
if (value > max_values.no_fate) then
max_values.no_fate = value
max_values.no_fate_count = 1
elseif (value == max_values.no_fate) then
max_values.no_fate_count = max_values.no_fate_count + 1
end
end
end
end
end
return max_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",
["Fate"] = "Fate Items",
["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
if (regular_q[q]) then
cat = cat .. "[[Category:" .. q .. " Items]]"
end
end
end
return cat
end
function p.categories(frame)
local name = get_arg(frame, "name")
local class = get_arg(frame, "class")
return p.categories_body(name, class)
end
return p