Module:Skill cap
From HorizonXI Wiki
Documentation for this module may be created at Module:Skill cap/doc
--[[
A module for functions relating to skill caps.
]]
local skill_cap_table = {
-- Level 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
["A+"] = {6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 158, 163, 168, 173, 178, 183, 188, 193, 198, 203, 207, 212, 217, 222, 227, 232, 236, 241, 246, 251, 256, 261, 266, 271, 276},
["A-"] = {6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 158, 163, 168, 173, 178, 183, 188, 193, 198, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 244, 249, 254, 259, 264, 269},
["B+"] = {5, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 123, 126, 129, 132, 135, 138, 141, 144, 147, 151, 156, 161, 166, 171, 176, 181, 186, 191, 196, 199, 203, 207, 210, 214, 218, 221, 225, 229, 233, 237, 242, 246, 251, 256},
["B" ] = {5, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 123, 126, 129, 132, 135, 138, 141, 144, 147, 151, 156, 161, 166, 171, 176, 181, 186, 191, 196, 199, 202, 205, 208, 212, 215, 218, 221, 225, 228, 232, 236, 241, 245, 250},
["B-"] = {5, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 123, 126, 129, 132, 135, 138, 141, 144, 147, 151, 156, 161, 166, 171, 176, 181, 186, 191, 196, 198, 201, 204, 206, 209, 212, 214, 217, 220, 223, 226, 229, 233, 236, 240},
["C+"] = {5, 7, 10, 13, 16, 19, 21, 24, 27, 30, 33, 35, 38, 41, 44, 47, 49, 52, 55, 58, 61, 63, 66, 69, 72, 75, 77, 80, 83, 86, 89, 91, 94, 97, 100, 103, 105, 108, 111, 114, 117, 119, 122, 125, 128, 130, 133, 136, 139, 142, 146, 151, 156, 161, 166, 170, 175, 180, 185, 190, 192, 195, 197, 200, 202, 205, 207, 210, 212, 215, 218, 221, 224, 227, 230},
["C" ] = {5, 7, 10, 13, 16, 19, 21, 24, 27, 30, 33, 35, 38, 41, 44, 47, 49, 52, 55, 58, 61, 63, 66, 69, 72, 75, 77, 80, 83, 86, 89, 91, 94, 97, 100, 103, 105, 108, 111, 114, 117, 119, 122, 125, 128, 130, 133, 136, 139, 142, 146, 151, 156, 161, 166, 170, 175, 180, 185, 190, 192, 194, 196, 199, 201, 203, 205, 208, 210, 212, 214, 217, 219, 222, 225},
["C-"] = {5, 7, 10, 13, 16, 19, 21, 24, 27, 30, 33, 35, 38, 41, 44, 47, 49, 52, 55, 58, 61, 63, 66, 69, 72, 75, 77, 80, 83, 86, 89, 91, 94, 97, 100, 103, 105, 108, 111, 114, 117, 119, 122, 125, 128, 130, 133, 136, 139, 142, 146, 151, 156, 161, 166, 170, 175, 180, 185, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220},
["D" ] = {4, 6, 9, 12, 14, 17, 20, 22, 25, 28, 31, 33, 36, 39, 41, 44, 47, 49, 52, 55, 58, 60, 63, 66, 68, 71, 74, 76, 79, 82, 85, 87, 90, 93, 95, 98, 101, 103, 106, 109, 112, 114, 117, 120, 122, 125, 128, 130, 133, 136, 140, 145, 150, 154, 159, 164, 168, 173, 178, 183, 184, 186, 188, 190, 192, 194, 195, 197, 199, 201, 202, 204, 206, 208, 210},
["E" ] = {4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 29, 31, 34, 36, 39, 41, 44, 46, 49, 51, 54, 56, 59, 61, 64, 66, 69, 71, 74, 76, 79, 81, 84, 86, 89, 91, 94, 96, 99, 101, 104, 106, 109, 111, 114, 116, 119, 121, 124, 126, 130, 135, 139, 144, 148, 153, 157, 162, 166, 171, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200},
["F" ] = {4, 6, 8, 10, 13, 15, 17, 20, 22, 24, 27, 29, 31, 33, 36, 38, 40, 43, 45, 47, 50, 52, 54, 56, 59, 61, 63, 66, 68, 70, 73, 75, 77, 79, 82, 84, 86, 89, 91, 93, 96, 98, 100, 102, 105, 107, 109, 112, 114, 116, 120, 124, 128, 133, 137, 141, 146, 150, 154, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189}
}
local p = {}
--[[
cap_to_rank
Get the letter-grade rank, given the level 75 skill cap as the first parameter.
Example:
{{#invoke:Skill cap|cap to rank|276}}
]]
local function lookup_rank(search_value)
if not search_value then
return "invalid rank"
end
for rank, skill_caps in pairs(skill_cap_table) do
if skill_caps[75] == search_value then
return rank
end
end
return "invalid rank"
end
function p.cap_to_rank(frame)
local search_value = tonumber(frame.args[1])
return lookup_rank(search_value)
end
p["cap to rank"] = p.cap_to_rank -- alias
--[[
get_cap
Get the skill cap, given the skill rank (A+ through F) as the first parameter,
and the level as the second parameter.
You may also use the level 75 skill cap as an alias for the letter-grade rank.
For example, 276 is an alias for A+.
Example:
{{#invoke:Skill cap|get cap|A+|37}}
{{#invoke:Skill cap|get cap|276|37}}
]]
local function lookup_cap(rank, level)
if tonumber(rank) then -- numerical alias for letter-grade
rank = lookup_rank(tonumber(rank))
end
if not rank or not skill_cap_table[rank] then
return "invalid rank"
end
if not level or not skill_cap_table[rank][level] then
return "invalid level"
end
return skill_cap_table[rank][level]
end
function p.get_cap(frame)
local rank = frame.args[1]
local level = tonumber(frame.args[2])
return lookup_cap(rank, level)
end
p["get cap"] = p.get_cap -- alias
--[[
find_minimum_level
Get the minimum level required, given the skill rank (A+ through F) as the first
parameter, and the desired skill level as the second parameter.
You may also use the level 75 skill cap as an alias for the letter-grade rank.
For example, 276 is an alias for A+.
Example:
{{#invoke:Skill cap|find minimum level|A+|250}}
{{#invoke:Skill cap|find minimum level|276|250}}
]]
function p.find_minimum_level(frame)
local skill_rank = frame.args[1]
local skill_level = tonumber(frame.args[2])
if tonumber(skill_rank) then -- numerical alias for letter-grade
skill_rank = lookup_rank(tonumber(skill_rank))
end
if not skill_rank or not skill_cap_table[skill_rank] then
return "invalid rank"
end
if not skill_level then
return "invalid level"
end
local arr = skill_cap_table[skill_rank]
-- Do a binary search for the first number in the the skill cap table
-- greater than or equal to skill_level
local left, right = 1, 75
local result = nil
while left <= right do
local mid = math.floor((left + right) / 2)
if arr[mid] >= skill_level then
result = mid -- potential candidate
right = mid - 1 -- search left side for earlier occurrence
else
left = mid + 1 -- search right side
end
end
return result
end
p["find minimum level"] = p.find_minimum_level -- alias
--[[
job_skill_caps_table
Displays a job's skill caps for every skill and every level, given the job name
as a parameter. Uses the Cargo table JobSkills, which holds data set by the
{{job skills}} template on each job page.
Example:
{{#invoke:Skill cap|job skill caps table|Warrior}}
]]
function p.job_skill_caps_table(frame)
local job = frame.args[1]
if not job then
return "invalid job"
end
local cargo_result = mw.ext.cargo.query(
"JobSkills",
"Skill,SkillCap",
{ where = "Job='" .. job .. "'" }
)
if not cargo_result then
return "invalid job"
end
local skills = {}
--[[
condense the Cargo result into a single table indexed by skill name
cargo_result:
{ { Skill="Archery", SkillCap=269 }, { Skill="Axe", SkillCap=240 }, ... }
skills:
{ Archery=269, Axe=240, ... }
]]
for _, cargo_row in ipairs(cargo_result) do
skills[cargo_row["Skill"]] = cargo_row["SkillCap"]
end
local skill_sets =
{
{"Hand-to-Hand", "Dagger", "Sword", "Great Sword", "Axe",
"Great Axe", "Scythe", "Polearm", "Katana", "Great Katana",
"Club", "Staff"
},
{"Archery", "Marksmanship", "Throwing"},
{"Guarding", "Evasion", "Shield", "Parrying"},
{"Divine", "Healing", "Enhancing", "Enfeebling", "Elemental",
"Dark", "Summon", "Ninjutsu", "Singing", "String", "Wind",
"Blue"
}
}
-- prune empty columns from skill_sets, iterating backwards
for i = #skill_sets, 1, -1 do
for j = #skill_sets[i], 1, -1 do
if not skills[skill_sets[i][j]] then
table.remove(skill_sets[i], j)
end
end
if #skill_sets[i] == 0 then
table.remove(skill_sets, i)
end
end
-- render the table
local root = mw.html.create("table")
:attr("id", "skill-caps")
:addClass("horizon-table jobs-table mw-collapsible mw-collapsed sortable")
:css("display", "block")
:css("margin", "auto")
:css("border", "none")
:css("width", "fit-content")
:css("max-height", "20lh")
:css("overflow", "auto")
:css("text-align", "center")
:css("background", "transparent")
-- Table caption
root
:tag("caption")
:css("margin", "auto")
:css("width", "20ch")
:wikitext("Skill Caps")
:done()
-- Table header row
local header_row = root:tag("tr")
-- Level header
header_row
:tag("th")
:attr("scope", "col")
:css("position", "sticky")
:css("top", "0")
:css("left", "0")
:css("z-index", "1")
:wikitext("Level")
:done()
for _, skill_list in ipairs(skill_sets) do
for i, s in ipairs(skill_list) do
local header = header_row
:tag("th")
:attr("scope", "col")
:addClass("unsortable")
:css("position", "sticky")
:css("top", "0")
:css("line-height", "1.6")
:css("writing-mode", "sideways-lr")
:css("vertical-align", "bottom")
:css("text-align", "left")
if i == 1 then
header:css("border-left-width", "3px")
end
if s == "Blue" then
header:addClass("toau")
end
header:wikitext(s)
header:done()
end
end
header_row:done()
-- Table rows
for level=1,75 do
local row = root:tag("tr")
row
:tag("td")
:css("position", "sticky")
:css("left", "0")
:wikitext(level)
:done()
for _, skill_list in ipairs(skill_sets) do
for i, s in ipairs(skill_list) do
local cell = row:tag("td")
if i == 1 then
cell:css("border-left-width", "3px")
end
if s == "Blue" then
cell:addClass("toau")
end
cell:wikitext(lookup_cap(skills[s], level))
cell:done()
end
end
row:done()
end
root:done()
return tostring(root)
end
p["job skill caps table"] = p.job_skill_caps_table -- alias
return p
