--[[Category:Flower Knight description modules]]
--[[ Displays abilities in relation to characters that own them.
Technical notes: In order to do this, the script loops over all characters,
looks at all evolution tiers of the character, and then notes all six
potential abilities the character could have (two bundled abilities that
store three individual abilities each).
The main table stores hashed individual abilities to character-evolution tier pairs.
ability_owners = {ability_hash : matching_characters}
matching_characters is a table of {character_name_jp, evo_tier}.
Furthermore, another table lets you do a reverse lookup of the hash.
hash_origins = {ability_hash : {base_id, val0, val1, val2}}.
This is for generating the ability descriptions.
Finally, a third table tracks which hashes are related to which base ability IDs.
hashes_by_base_id = {base_ability_id : ability_hash}
This is for grouping of abilities in the output.
Some characters get new or better abilities when going up an evolution tier.
A note is added for abilities that are added/buffed at the blooming stage
compared to any previous stage. Similarly, added/buffed abilities at the rarity growth
stage are noted when they differ from the pre-evo, evo, or blooming stages.
local p = {}
local get_args = require('Module:Arguments').getArgs
local categories = require('Module:Categories')
local utils = require('Module:UtilityFunctions')
p.masterCData = require('Module:MasterCharacterData')
p.list_header = [[{| class="article-table sortable"
|+Comprehensive Ability List
! scope="col"|Type
! scope="col"|Description
! scope="col"|Characters
p.list_row = [[|${name}
-- Gives abbreviations for evolution tiers based on the
-- character's original rarity.
p.tier_abbr_by_rarity = {
    [2] = {"", "'''E''' ", "'''RG''' ", "'''RG+B''' "},
    [3] = {"", "'''E''' ", "'''RG''' ", "'''RG+B''' "},
    [4] = {"", "'''E''' ", "'''RG''' ", "'''RG+B''' "},
    [5] = {"", "'''E''' ", "'''B''' ", "'''RG''' "},
    [6] = {"", "'''E''' ", "'''B''' ", "? "},
-- Functions accessed from templates.
-- Displays the comprehensive list of abilities.
-- Ignores all args.
function p.comprehensive(frame)
    local args = get_args(frame:getParent())
    return p._comprehensive(args)
-- Misc functions and functions for supporting the template-called functions.
--[[ Loads a module if it hasn't been loaded yet.
@arg mod_name: String. The name of the module without the "Module:" part.
@returns True if the module was loaded for the first time.
    False if the module was already available.
function p.__load_module(mod_name)
    if not p[mod_name] then
        p[mod_name] = require('Module:' .. mod_name)
        return true
    return false
--[[ Sorting function for p.ability_pairs.
function p.__sort_ability(x, y)
    if x.base ~= y.base then
        return x.base < y.base
    elseif x[0] ~= y[0] then
        return x[0] < y[0]
    elseif x[1] ~= y[1] then
        return x[1] < y[1]
    elseif x[2] ~= y[2] then
        return x[2] < y[2]
    return x[3] < y[3]
--[[ Iterator function for looping over abilities.
function p.ability_pairs(origins)
    -- Convert hash to array.
    local t = {}
    for k, v in pairs(origins) do
        table.insert(t, v)
    -- Sort by values.
    table.sort(t, p.__sort_ability)
    return t
--[[ Hashes an individual ability.
It takes the 4 values of the ability and turns them into a string.
@param base: Int. The base ability ID.
@param val0: Int. Val 0 of the ability.
@param val1: Int. Val 1 of the ability.
@param val2: Int. Val 2 of the ability.
@param val3: Int. Val 3 of the ability.
@returns String. The hash value.
function p.__hash_individual_ability(base, val0, val1, val2, val3)
    return string.format('%d,%d,%d,%d,%d', base, val0, val1, val2, val3)
--[[ Adds an individual ability to the conglomerative list of abilities.
@param ability_owners: 2-layered table. The ability associations.
@param hash_origins: 1-layered table. Relates hash strings to the original data.
@param base_id: Int. The base ability ID.
@param val0: Int. Val 0 of the ability.
@param val1: Int. Val 1 of the ability.
@param val2: Int. Val 2 of the ability.
@param val3: Int. Val 3 of the ability.
@param owner: String or Int. Whatever data you want to store which
    relates to the specific ability values. Good examples are the character's
    Japanese name or ID.
@param evo_tier: Int. The evolution tier of the character. Valid values: 1 - 4.
@param char_data: Table. The character's affiliated master data.
@param less Boolean. If true, removes a lot of unnecessary entries.
    Disabling it might be good for debugging. Default: True.
@returns String? The hash of the individual ability.
function p.__add_bundled_ability_to_assoc_table(ability_owners, hash_origins,
    hashes_by_base_id, base_id, val0, val1, val2, val3, owner, evo_tier, char_data,
    -- This ability does nothing.
    if base_id <= 0 then
    -- Create the hash and initialize any lists we need.
    hash = p.__hash_individual_ability(base_id, val0, val1, val2, val3)
    if not ability_owners[hash] then
        -- Create a new table of characters related to this individual ability.
        ability_owners[hash] = {}
        -- Keep track of how we made the hash in the first place.
        hash_origins[hash] = {base=base_id, [0]=val0, [1]=val1, [2]=val2, [3]=val3}
    if not hashes_by_base_id[base_id] then
        -- Create a new table of hashes related to this base ability.
        hashes_by_base_id[base_id] = {}
    -- Add the ability to all the tables.
    -- If it was already earned by the character in a previous evolution tier,
    -- and "less" is set, don't re-add the ability.
    if not less or not ability_owners[hash][owner] then
        ability_owners[hash][owner] = {evo_tier = evo_tier, rarity = char_data.rarity}
    hashes_by_base_id[base_id][hash] = 1
    return hash
--[[ Makes a table relating ability values and characters that have them.
Example output is like so.
assocs = p.__get_conglomerative_abilities({})
assocs[101] => Base ID 101 is for Def Up.
assocs[101]['3,2,40'] => This refers to Def Up abilities with
    val0 = 3, val1 = 2, val2 = 40. The result of this call is a table
    of strings set to whatever "owner" of p.__add_bundled_ability_to_assoc_table
    is passed. Probably the character's Japanese name.
@param less Boolean. If true, removes a lot of unnecessary entries.
    Disabling it might be good for debugging. Default: True.
@returns ability_owners, hash_origins, hashes_by_base_id. All three are described
    at the top of this page.
function p.__get_conglomerative_abilities(less)
    ability_owners = {}
    hash_origins = {}
    hashes_by_base_id = {}
    if less == nil then less = true end
    -- Loop over all characters.
    for name_jp, char_data in pairs(p.MasterCharacterData) do
        -- Loop over the bundled abilities for each evolution tier.
        for evo_tier, bundled_ability_ids in ipairs(char_data.bundledAbilities) do
            -- Loop over the two bundled abilities to get the
            -- individual IDs. It is a loop in case the master data gets
            -- more bundled abilities per character in the future.
            for _, individual_ability_id in pairs(bundled_ability_ids) do
                -- Select the bundled ability out of the master list.
                bundle = p.BundledAbilityList[individual_ability_id]
                -- Add all three (potential) individual abilities to the table.
                -- Some of these abilities may be ability 0 (= no ability).
                if bundle then
                        hash_origins, hashes_by_base_id,
                        bundle.ability1ID, bundle.ability1Val0,
                        bundle.ability1Val1, bundle.ability1Val2,
                        bundle.ability1Val3, name_jp, evo_tier, char_data, less)
                        hash_origins, hashes_by_base_id,
                        bundle.ability2ID, bundle.ability2Val0,
                        bundle.ability2Val1, bundle.ability2Val2,
                        bundle.ability2Val3, name_jp, evo_tier, char_data, less)
                        hash_origins, hashes_by_base_id,
                        bundle.ability3ID, bundle.ability3Val0,
                        bundle.ability3Val1, bundle.ability3Val2,
                        bundle.ability3Val3, name_jp, evo_tier, char_data, less)
                end -- if bundle
            end -- for pairs(bundled_ability_ids)
        end -- for pairs(char_data.bundledAbilities)
    end -- for pairs(p.MasterCharacterData)
    return ability_owners, hash_origins, hashes_by_base_id
-- Functions called through templates. These ones have the args pre-formatted.
function p._comprehensive(args)
    ability_owners, hash_origins, hashes_by_base_id =
    local ret_table = {p.list_header}
    -- Loop over all hashes.
    local sorted_origins = p.ability_pairs(hash_origins)
    local baseAbil = p['Ability/Data']['baseAbilities']
    for _, ability_data in pairs(sorted_origins) do
        baseAbilDefn = baseAbil[ability_data.base]
        -- Only work with base abilities defined in Ability/Data
        if baseAbilDefn ~= nil then
            mw.log(temp, ability_data, ability_data.base, ability_data[0],
                ability_data[1], ability_data[2], ability_data[3])
            format_args = {
                name = baseAbil[ability_data.base]['name'],
                desc = '',
                chars = '',
            -- Make the ability description.
            format_args.desc = p['Ability']._getIndividualDesc(
                ability_data.base, ability_data[0], ability_data[1],
                ability_data[2], ability_data[3], true)
            -- Loop over all characters/evolution stages with this hash.
            -- The data will be organized by rarity, then by English name.
            hash = p.__hash_individual_ability(ability_data.base, ability_data[0],
                ability_data[1], ability_data[2], ability_data[3])
            chars_by_rarity = {[2]={}, [3]={}, [4]={}, [5]={}, [6]={},}
            for character, char_tuples in pairs(ability_owners[hash]) do
                -- Add all characters with this individual ability to the table.
                rarity = char_tuples.rarity
                evo_tier = char_tuples.evo_tier
                if (rarity <= 4 and evo_tier >= 3) or (rarity == 5 and evo_tier == 4) then
                    -- This character was rarity grown to a 6*.
                    rarity = 6
                -- Store the character based on their English name.
                names = p['CharacterNames'][character]
                main = names and names['main'] or ''
                -- Do not include the character if their names data has "skip" set.
                if not(names and names.skip) then
                    -- Include this character!
                    chars_by_rarity[rarity][main] = char_tuples
            end -- end of for pairs(ability_owners[hash])
            -- Loop over rarities in order.
            for rarity, characters in utils.orderedPairs(chars_by_rarity) do
                -- Loop over the sorted English names.
                for character, char_tuples in utils.orderedPairs(characters) do
                    evo_tier = char_tuples.evo_tier
                    -- Determine how to display the character.
                    qualifier = p.tier_abbr_by_rarity[char_tuples.rarity][evo_tier]
                    format_args.chars = format_args.chars ..
                        string.format('%s%d* [[%s]], ', qualifier, rarity, character)
                end -- for utils.orderedPairs(characters)
            end -- for ipairs(chars_by_rarity)
            -- Remove the last ", " from the characters list.
            format_args.chars = string.sub(format_args.chars, 1, -3)
            table.insert(ret_table, utils.format(p.list_row, format_args))
        end -- if baseAbilDefn ~= nil then
    end -- for _, hash in p.ability_pairs(hash_origins) do
    table.insert(ret_table, '|}')
    return table.concat(ret_table)
return p
Community content is available under CC-BY-SA unless otherwise noted.