Module:Skill: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
>OmegaK2
mNo edit summary
>OmegaK2
(Port of p.skill to cargo, switched arg output to a table (tpl_args.skill_levels), support for i18n args)
Line 6: Line 6:


local mwlanguage = mw.language.getContentLanguage()
local mwlanguage = mw.language.getContentLanguage()
--
-- Data
--
local i18n = {
    args = {
        -- skills
        skill_id = 'skill_id',
        is_support_gem = 'is_support_gem',
        support_gem_letter = 'support_gem_letter',
        cast_time = 'cast_time',
        gem_description = 'gem_description',
        active_skill_name = 'active_skill_name',
        item_class_restriction = 'item_class_restriction',
        projectile_speed = 'projectile_speed',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
        has_percentage_mana_cost = 'has_percentage_mana_cost',
        has_reservation_mana_cost = 'has_reservation_mana_cost',
        radius = 'radius',
        radius_description = 'radius_description',
        radius_secondary = 'radius_secondary',
        radius_secondary_description = 'radius_secondary_description',
        radius_tertiary = 'radius_tertiary',
        radius_tertiary_description = 'radius_tertiary_description',
        -- skill_levels
        level_requirement = 'level_requirement',
        dexterity_requirement = 'dexterity_requirement',
        intelligence_requirement = 'intelligence_requirement',
        strength_requirement = 'strength_requirement',
        mana_multiplier = 'mana_multiplier',
        critical_strike_chance = 'critical_strike_chance',
        mana_cost = 'mana_cost',
        damage_effectiveness = 'damage_effectiveness',
        stored_uses = 'stored_uses',
        cooldown = 'cooldown',
        vaal_souls_requirement = 'vaal_souls_requirement',
        vaal_stored_uses = 'vaal_stored_uses',
        damage_multiplier = 'damage_multiplier',
        experience = 'experience',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
       
        -- skill_stats_per_level
        id = 'id',
        value = 'value',
       
        -- prefixes
        prefix_level = 'level',
        prefix_static = 'static_',
        prefix_quality_stat = 'quality_',
        infix_stat = 'stat'
    },
}


--
--
Line 23: Line 79:
end
end


data.map = {}
local map = {}
data.map.stats = {
map.stats = {
     {
     {
         prefix = '',
         prefix_in = '',
         property_prefix = 'Has stat ',
         out = 'stats',
        quality = false,
     },
     },
     {
     {
         prefix = 'quality_',
         prefix_in = i18n.args.prefix_quality_stat,
         property_prefix = 'Has quality stat ',
        out = 'quality_stats',
         quality = true,
     },
     },
}
}


data.map.static = {
map.static = {
     -- GrantedEffects.dat
     table = 'skills',
    {
    fields = {
        name = 'skill_id',
        -- GrantedEffects.dat
        property = 'Is skill id',
        skill_id = {
        func = nil,
            name = i18n.args.skill_id,
    },
            field = 'skill_id',
    {
            type = 'String',
        name = 'is_support_gem',
            func = nil,
        property = 'Is support gem',
        },
        func = data.cast.wrap(m_util.cast.boolean),
        is_support_gem = {
    },
            name = i18n.args.is_support_gem,
    {
            field = 'is_support_gem',
        name = 'support_gem_letter',
            type = 'Boolean',
        property = 'Has support gem letter',
            func = data.cast.wrap(m_util.cast.boolean),
        func = nil,
        },
    },
        support_gem_letter = {
    -- Active Skills.dat
            name = i18n.args.support_gem_letter,
    {
            field = 'support_gem_letter',
        name = 'cast_time',
            type = 'String(size=2)',
        property = 'Has cast time',
            func = nil,
        func = data.cast.wrap(m_util.cast.number),
        },
    },
        -- Active Skills.dat
    {
        cast_time = {
        name = 'gem_description',
            name = i18n.args.cast_time,
        property = 'Has description',
            field = 'cast_time',
        func = nil,
            type = 'Integer',
    },
            func = data.cast.wrap(m_util.cast.number),
    {
        },
        name = 'active_skill_name',
        gem_description = {
        property = 'Has active skill name',
            name = i18n.args.gem_description,
        func = nil,
            field = 'gem_description',
    },
            type = 'Text',
    {
            func = nil,
        name = 'item_class_restriction',
        },
        property = 'Has item class restrictions',
        active_skill_name = {
        func = function(tpl_args, frame, value)
            name = i18n.args.active_skill_name,
            if value == nil then  
            field = 'active_skill_name',
                return nil
            type = 'String',
            end
            func = nil,
            value = m_util.string.split(value, ', ')
        },
            for _, v in ipairs(value) do
        item_class_restriction = {
                local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
            name = i18n.args.item_class_restriction,
                if result == nil then
            field = 'item_class_restriction',
                    error(string.format('Invalid item class: %s', v))
            type = 'List (,) of String',
            func = function(tpl_args, frame, value)
                if value == nil then  
                    return nil
                end
                value = m_util.string.split(value, ', ')
                for _, v in ipairs(value) do
                    local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
                    if result == nil then
                        error(string.format('Invalid item class: %s', v))
                    end
                 end
                 end
             end
                return value
             return value
             end,
         end,
        },
        -- Projectiles.dat - manually mapped to the skills
        projectile_speed = {
            name = i18n.args.projectile_speed,
            field = 'projectile_speed',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        -- Misc data derieved from stats
        stat_text = {
            name = i18n.args.stat_text,
            field = 'stat_text',
            type = 'Text',
            func = nil,
        },
        quality_stat_text = {
            name = i18n.args.quality_stat_text,
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
        },
        -- Misc data currently not from game data
        has_percentage_mana_cost = {
            name = i18n.args.has_percentage_mana_cost,
            field = 'has_percentage_mana_cost',
            type = 'Boolean',
            func = data.cast.wrap(m_util.cast.boolean),
        },
        has_reservation_mana_cost = {
            name = i18n.args.has_reservation_mana_cost,
            field = 'has_reservation_mana_cost',
            type = 'Boolean',
            func = data.cast.wrap(m_util.cast.boolean),
        },
        radius = {
            name = i18n.args.radius,
            field = 'radius',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_description = {
            name = i18n.args.radius_description,
            field = 'radius_description',
            type = 'Text',
            func = nil,
        },
        radius_secondary = {
             name = i18n.args.radius_secondary,
            field = 'radius_secondary',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_secondary_description = {
            name = i18n.args.radius_secondary_description,
            field = 'radius_secondary_description',
            type = 'Text',
            func = nil,
        },
        -- not sure if any skill actually has 3 radius componets
        radius_tertiary = {
            name = i18n.args.radius_tertiary,
            field = 'radius_tertiary',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_tertiary_description = {
            name = i18n.args.radius_tertiary_description,
            field = 'radius_tertiary_description',
            type = 'Text',
            func = nil,
        },
        -- Set manually
        max_level = {
            field = 'max_level',
            type = 'Integer',
         },
     },
     },
    {
}
        name = 'item_class_restriction_difference',
        property = 'Has item class restrictions difference',
        func = function(tpl_args, frame, value)
            if tpl_args.item_class_restriction == nil then
                return nil
            end
           
            local classes = {}
            for _, class in ipairs(tpl_args.item_class_restriction) do
                classes[class] = true
            end


             local rtr = {}
map.progression = {
             for _, data in ipairs(m_game.constants.item.class) do
    table = 'skill_levels',
                if classes[data.full] == nil then
    fields = {
                     rtr[#rtr+1] = data.full
        level = {
            name = nil,
             field = 'level',
            type = 'Integer',
            func = nil,
            header = nil,
        },
        level_requirement = {
            name = i18n.args.level_requirement,
            field = 'level_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
        },
        dexterity_requirement = {
            name = i18n.args.dexterity_requirement,
            field = 'dexterity_requirement',
             type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
        },
        strength_requirement = {
            name = i18n.args.strength_requirement,
            field = 'strength_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
        },
        intelligence_requirement = {
            name = i18n.args.intelligence_requirement,
            field = 'intelligence_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
        },
        mana_multiplier = {
            name = i18n.args.mana_multiplier,
            field = 'mana_multiplier',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Mana<br>Multiplier',
            fmt = '%s%%',
        },
        critical_strike_chance = {
            name = i18n.args.critical_strike_chance,
            field = 'critical_strike_chance',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Critical<br>Strike<br>Chance',
            fmt = '%s%%',
        },
        mana_cost = {
            name = i18n.args.mana_cost,
            field = 'mana_cost',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = function (skill_data)
                if skill_data["Has reservation mana cost"] then
                     return 'Mana<br>Reserved'
                else
                    return 'Mana<br>Cost'
                end
            end,
            fmt = function (tpl_args, frame, value)
                local str
                if tpl_args.has_percentage_mana_cost then
                    str = '%s%%'
                else
                    str = '%s'
                 end
                 end
            end
              
              
            return rtr
                return string.format(str, value)
        end
            end,
    },
        },
    {
        damage_effectiveness = {
        name = 'no_item_class_restriction',
            name = i18n.args.damage_effectiveness,
        property = 'Has no item class restrictions',
            field = 'damage_effectiveness',
        func = function(tpl_args, frame, value)
            type = 'Float',
             if tpl_args['item_class_restriction'] == nil then
            func = data.cast.wrap(m_util.cast.number),
                return true
             header = 'Damage<br>Effectiveness',
             else
             fmt = '%s%%',
                return nil
         },
            end
        stored_uses = {
         end,
            name = i18n.args.stored_uses,
    },
            field = 'stored_uses',
    -- Projectiles.dat - manually mapped to the skills
            type = 'Integer',
    {
            func = data.cast.wrap(m_util.cast.number),
        name = 'projectile_speed',
            header = 'Stored<br>Uses',
        property = 'Has projectile speed',
        },
        func = data.cast.wrap(m_util.cast.number),
        cooldown = {
    },
            name = i18n.args.cooldown,
    -- Misc data derieved from stats
            field = 'cooldown',
    {
            type = 'Float',
        name = 'stat_text',
            func = data.cast.wrap(m_util.cast.number),
        property = 'Has stat text',
            header = 'Cooldown',
        func = nil,
            fmt = '%ss',
    },
         },
    {
        vaal_souls_requirement = {
        name = 'quality_stat_text',
            name = i18n.args.vaal_souls_requirement,
        property = 'Has quality stat text',
            field = 'vaal_souls_requirement',
         func = nil,
            type = 'Integer',
    },
            func = data.cast.wrap(m_util.cast.number),
    -- Misc data currently not from game data
            header = 'Vaal<br>souls',
    {
        },
        name = 'has_percentage_mana_cost',
        vaal_stored_uses = {
        property = 'Has percentage mana cost',
            name = i18n.args.vaal_stored_uses,
        func = data.cast.wrap(m_util.cast.boolean),
            field = 'vaal_stored_uses',
    },
            type = 'Integer',
    {
            func = data.cast.wrap(m_util.cast.number),
        name = 'has_reservation_mana_cost',
            header = 'Stored<br>Uses',
        property = 'Has reservation mana cost',
        },
        func = data.cast.wrap(m_util.cast.boolean),
        damage_multiplier = {
    },
            name = i18n.args.damage_multiplier,
    {
            field = 'damage_multiplier',
        name = 'radius',
            type = 'Float',
        property = 'Has primary radius',
            func = data.cast.wrap(m_util.cast.number),
        func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
    },
            fmt = '%s%%',
    {
         },
        name = 'radius_description',
        -- from gem experience, optional
        property = 'Has primary radius description',
        experience = {
         func = nil,
            name = i18n.args.experience,
    },
            field = 'experience',
    {
            type = 'Integer',
        name = 'radius_secondary',
            func = data.cast.wrap(m_util.cast.number),
        property = 'Has secondary radius',
            hide = true,
        func = data.cast.wrap(m_util.cast.number),
        },
    },
        stat_text = {
    {
            name = i18n.args.stat_text,
        name = 'radius_secondary_description',
            field = 'stat_text',
        property = 'Has secondary radius description',
            type = 'Text',
        func = nil,
            func = nil,
    },
            hide = true,
    -- not sure if any skill actually has 3 radius componets
         },
    {
         quality_stat_text = {
         name = 'radius_tertiary',
            name = i18n.args.quality_stat_text,
         property = 'Has tertiary radius',
            field = 'quality_stat_text',
        func = data.cast.wrap(m_util.cast.number),
            type = 'Text',
    },
            func = nil,
    {
            hide = true,
        name = 'radius_tertiary_description',
        },
        property = 'Has tertiary radius description',
     }
        func = nil,
     },
}
}


data.map.progression = {
map.skill_stats_per_level = {
     {
     table = 'skill_stats_per_level',
        name = 'level_requirement',
     fields = {
        property = 'Has level requirement',
         level = {
        func = data.cast.wrap(m_util.cast.number),
            field = 'level',
        header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
            type = 'Integer',
     },
         },
    {
         id = {
        name = 'dexterity_requirement',
            field = 'id',
        property = 'Has dexterity requirement',
            type = 'String',
        func = data.cast.wrap(m_util.cast.number),
         },
        header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
         value = {
    },
             field = 'value',
        {
            type = 'Integer',
         name = 'strength_requirement',
         },
        property = 'Has strength requirement',
         is_quality_stat = {
        func = data.cast.wrap(m_util.cast.number),
            field = 'is_quality_stat',
        header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
            type = 'Boolean',
    },
         },
        {
        name = 'intelligence_requirement',
        property = 'Has intelligence requirement',
         func = data.cast.wrap(m_util.cast.number),
        header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
    },
    {
        name = 'mana_multiplier',
        property = 'Has mana multiplier',
        func = data.cast.wrap(m_util.cast.number),
        header = 'Mana<br>Multiplier',
         fmt = '%s%%',
    },
    {
        name = 'critical_strike_chance',
        property = 'Has critical strike chance',
         func = data.cast.wrap(m_util.cast.number),
        header = 'Critical<br>Strike<br>Chance',
        fmt = '%s%%',
    },
    {
        name = 'mana_cost',
        property = 'Has mana cost',
        func = data.cast.wrap(m_util.cast.number),
        header = function (skill_data)
            if skill_data["Has reservation mana cost"] then
                return 'Mana<br>Reserved'
            else
                return 'Mana<br>Cost'
            end
        end,
         fmt = function (tpl_args, frame, value)
            local str
            if tpl_args.has_percentage_mana_cost then
                str = '%s%%'
             else
                str = '%s'
            end
       
            return string.format(str, value)
        end,
    },
    {
        name = 'damage_effectiveness',
        property = 'Has damage effectiveness',
        func = data.cast.wrap(m_util.cast.number),
        header = 'Damage<br>Effectiveness',
         fmt = '%s%%',
    },
    {
         name = 'stored_uses',
        property = 'Has stored uses',
        func = data.cast.wrap(m_util.cast.number),
        header = 'Stored<br>Uses',
    },
    {
        name = 'cooldown',
        property = 'Has cooldown',
         func = data.cast.wrap(m_util.cast.number),
        header = 'Cooldown',
        fmt = '%ss',
    },
    {
        name = 'vaal_souls_requirement',
        property = 'Has vaal souls requirement',
        func = data.cast.wrap(m_util.cast.number),
        header = 'Vaal<br>souls',
    },
    {
        name = 'vaal_stored_uses',
        property = 'Has vaal stored uses',
        func = data.cast.wrap(m_util.cast.number),
        header = 'Stored<br>Uses',
    },
    {
        name = 'damage_multiplier',
        property = 'Has damage multiplier',
        func = data.cast.wrap(m_util.cast.number),
        header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
        fmt = '%s%%',
    },
    -- from gem experience, optional
    {
        name = 'experience',
        property = 'Has experience requirement',
        func = data.cast.wrap(m_util.cast.number),
        hide = true,
    },
    {
        name = 'stat_text',
        property = 'Has stat text',
        func = nil,
        hide = true,
    },
    {
        name = 'quality_stat_text',
        property = 'Has quality stat text',
        func = nil,
        hide = true,
     },
     },
}
}
Line 306: Line 404:
local h = {}
local h = {}


function h.map_to_arg(tpl_args, frame, properties, prefix, map)
function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
    local id
     for key, row in pairs(map.fields) do
    local val
         if row.name then
     for _, row in ipairs(map) do
            local val = tpl_args[prefix_in .. row.name]
         id = prefix .. row.name
            if row.func ~= nil then
        val = tpl_args[id]
                val = row.func(tpl_args, frame, val)
        if row.func ~= nil then
            end
            val = row.func(tpl_args, frame, val)
            if val ~= nil then     
        end
                if level ~= nil then
        if val ~= nil then     
                    tpl_args.skill_levels[level][key] = val
            tpl_args[id] = val
                    -- Nuke variables since they're remapped to skill_levels
            properties[row.property] = val
                    tpl_args[prefix_in .. row.name] = nil
                else
                    tpl_args[row.name] = val
                end
                properties[row.field] = val
            end
         end
         end
     end
     end
end
end


function h.stats(tpl_args, frame, properties, prefix, level)
function h.stats(tpl_args, frame, prefix_in, level)
    local stat_ids
     for _, stat_type in ipairs(map.stats) do
    local stat_values
         local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
    local stat_id
         tpl_args.skill_levels[level][stat_type.out] = {}
    local stat_id_key
    local stat_val
    local stat_val_key
    local type_prefix
   
     for _, stat_type in ipairs(data.map.stats) do
         type_prefix = string.format('%s%sstat', prefix, stat_type.prefix)
         stat_ids = {}
        stat_values = {}
         for i=1, 8 do
         for i=1, 8 do
             stat_id_key = string.format('%s%s_id', type_prefix, i)
             local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
             stat_val_key = string.format('%s%s_value', type_prefix, i)
             local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
             stat_id = tpl_args[stat_id_key]
             local stat = {
            stat_val = tonumber(tpl_args[stat_val_key])
                id = tpl_args[stat_id_key],
             if stat_id ~= nil and stat_val ~= nil then
                value = tonumber(tpl_args[stat_val_key]),
                 tpl_args[stat_val_key] = stat_val
            }
                stat_ids[#stat_ids+1] = stat_id
             if stat.id ~= nil and stat.value ~= nil then
                 stat_values[#stat_values+1] = stat_val
                 tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
               
                 m_util.cargo.store(frame, {
                    _table = map.skill_stats_per_level.table,
                    [map.skill_stats_per_level.fields.level.field] = level,
                    [map.skill_stats_per_level.fields.id.field] = stat.id,
                    [map.skill_stats_per_level.fields.value.field] = stat.value,
                    [map.skill_stats_per_level.fields.is_quality_stat.field] = stat_type.quality,
                })
                  
                  
                 subobject = {}
                 -- Nuke variables since they're remapped to skill levels
                subobject['Has skill level'] = level
                 tpl_args[stat_id_key] = nil
                 subobject[stat_type.property_prefix .. 'id'] = stat_id
                 tpl_args[stat_val_key] = nil
                 subobject[stat_type.property_prefix .. 'value'] = stat_val
                m_util.smw.subobject(frame, string.format('level%s_stat%s_%s', level, i, stat_id), subobject)
             end
             end
         end
         end
        tpl_args[type_prefix .. 's_ids'] = stat_ids
        tpl_args[type_prefix .. 's_values'] = stat_values
       
        properties[stat_type.property_prefix .. 'ids'] = stat_ids
     end
     end
end
end
Line 390: Line 486:
-- For Template:Skill
-- For Template:Skill
--
--
p.table_skills = m_util.cargo.declare_factory{data=map.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=skill_stats_per_level}
function p.skill(frame, tpl_args)
function p.skill(frame, tpl_args)
     if tpl_args == nil then
     if tpl_args == nil then
Line 402: Line 502:
     --
     --
     local properties
     local properties
   
    tpl_args.skill_levels = {
        [0] = {},
    }
      
      
     -- Handle level progression
     -- Handle level progression
    local prefix
     for i=1, 30 do
     for i=1, 30 do
         properties = {}
         local prefix = i18n.args.prefix_level .. i  
        prefix = string.format('level%s', i)
         if m_util.cast.boolean(tpl_args[prefix]) == true then
         if m_util.cast.boolean(tpl_args[prefix]) == true then
            -- Don't need this anymore
            tpl_args[prefix] = nil
            tpl_args.skill_levels[i] = {}
             prefix = prefix .. '_'
             prefix = prefix .. '_'
             h.map_to_arg(tpl_args, frame, properties, prefix, data.map.progression)
             local prefix_out = string.format('level%s_', i)
            h.stats(tpl_args, frame, properties, prefix, i)
            properties['Is skill level'] = i
              
              
             m_util.smw.subobject(frame, 'level' .. i, properties)
             properties = {
                _table = map.progression.table,
                [map.progression.fields.level.field] = i
            }
            h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i)
            m_util.cargo.store(frame, properties)
           
            h.stats(tpl_args, frame, prefix, i)
              
              
             if tpl_args[prefix .. 'experience'] ~= nil then
             if tpl_args[prefix .. 'experience'] ~= nil then
Line 424: Line 534:
     -- Handle static arguments
     -- Handle static arguments
     properties = {
     properties = {
         ['Has maximum skill level'] = tpl_args.max_level
        _table = map.static.table,
         [map.static.fields.max_level.field] = tpl_args.max_level
     }
     }
      
      
     h.map_to_arg(tpl_args, frame, properties, '', data.map.static)
     h.map_to_arg(tpl_args, frame, properties, '', map.static)
     h.map_to_arg(tpl_args, frame, properties, 'static_', data.map.progression)
     h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
     h.stats(tpl_args, frame, properties, 'static_', 0)
     h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
     local copy = mw.clone(properties)
      
    -- add the properties to the page directly
     m_util.cargo.store(frame, properties)
     m_util.smw.set(frame, properties)
    -- also add them to their own subobject for convienence
    m_util.smw.subobject(frame, 'static', copy)
end
end


Line 506: Line 614:
     query = {}
     query = {}
     query[#query+1] = string.format('[[-Has subobject::%s]]', page)
     query[#query+1] = string.format('[[-Has subobject::%s]]', page)
     for _, pdata in ipairs(data.map.progression) do
     for _, pdata in ipairs(map.progression) do
         query[#query+1] = '?' .. pdata.property .. '#-'
         query[#query+1] = '?' .. pdata.property .. '#-'
     end
     end
Line 544: Line 652:
             :done()
             :done()
      
      
     for _, pdata in ipairs(data.map.progression) do
     for _, pdata in ipairs(map.progression) do
         -- TODO should be nil?
         -- TODO should be nil?
         if pdata.hide == nil and headers[pdata.property] then
         if pdata.hide == nil and headers[pdata.property] then
Line 588: Line 696:
                 :done()
                 :done()
          
          
         for _, pdata in ipairs(data.map.progression) do
         for _, pdata in ipairs(map.progression) do
             if pdata.hide == nil and headers[pdata.property] then
             if pdata.hide == nil and headers[pdata.property] then
                 h.int_value_or_na(tpl_args, frame, tblrow, row[pdata.property], pdata)
                 h.int_value_or_na(tpl_args, frame, tblrow, row[pdata.property], pdata)
Line 642: Line 750:
      
      
     return empty .. tostring(tbl)
     return empty .. tostring(tbl)
end
function p.map(arg)
    for key, data in pairs(map[arg].fields) do
        mw.logObject(key)
    end
end
end


return p
return p

Revision as of 17:25, 14 December 2017

Module documentation[view] [edit] [history] [purge]


Lua logo

This module depends on the following other modules:

Overview

Module for handling skills with semantic media wiki support

Skill templates

Module:Item2

All templates defined in Module:Skill:

Module:Skill link

All templates defined in Module:Skill link:

ru:Модуль:Skill

local p = {}

local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_game = require('Module:Game')

local mwlanguage = mw.language.getContentLanguage()

--
-- Data
--

local i18n = {
    args = {
        -- skills
        skill_id = 'skill_id',
        is_support_gem = 'is_support_gem',
        support_gem_letter = 'support_gem_letter',
        cast_time = 'cast_time',
        gem_description = 'gem_description',
        active_skill_name = 'active_skill_name',
        item_class_restriction = 'item_class_restriction',
        projectile_speed = 'projectile_speed',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
        has_percentage_mana_cost = 'has_percentage_mana_cost',
        has_reservation_mana_cost = 'has_reservation_mana_cost',
        radius = 'radius',
        radius_description = 'radius_description',
        radius_secondary = 'radius_secondary',
        radius_secondary_description = 'radius_secondary_description',
        radius_tertiary = 'radius_tertiary',
        radius_tertiary_description = 'radius_tertiary_description',

        -- skill_levels
        level_requirement = 'level_requirement',
        dexterity_requirement = 'dexterity_requirement',
        intelligence_requirement = 'intelligence_requirement',
        strength_requirement = 'strength_requirement',
        mana_multiplier = 'mana_multiplier',
        critical_strike_chance = 'critical_strike_chance',
        mana_cost = 'mana_cost',
        damage_effectiveness = 'damage_effectiveness',
        stored_uses = 'stored_uses',
        cooldown = 'cooldown',
        vaal_souls_requirement = 'vaal_souls_requirement',
        vaal_stored_uses = 'vaal_stored_uses',
        damage_multiplier = 'damage_multiplier',
        experience = 'experience',
        stat_text = 'stat_text',
        quality_stat_text = 'quality_stat_text',
        
        -- skill_stats_per_level
        id = 'id',
        value = 'value',
        
        -- prefixes
        prefix_level = 'level',
        prefix_static = 'static_',
        prefix_quality_stat = 'quality_',
        infix_stat = 'stat'
    },
}

--
-- Data
--

local data = {}
data.cast = {}
data.cast.wrap = function(f)
    return function(tpl_args, frame, value)
        if value == nil then
            return nil
        else
            return f(value)
        end
    end
end

local map = {}
map.stats = {
    {
        prefix_in = '',
        out = 'stats',
        quality = false,
    },
    {
        prefix_in = i18n.args.prefix_quality_stat,
        out = 'quality_stats',
        quality = true,
    },
}

map.static = {
    table = 'skills',
    fields = {
        -- GrantedEffects.dat
        skill_id = {
            name = i18n.args.skill_id,
            field = 'skill_id',
            type = 'String',
            func = nil,
        },
        is_support_gem = {
            name = i18n.args.is_support_gem,
            field = 'is_support_gem',
            type = 'Boolean',
            func = data.cast.wrap(m_util.cast.boolean),
        },
        support_gem_letter = {
            name = i18n.args.support_gem_letter,
            field = 'support_gem_letter',
            type = 'String(size=2)',
            func = nil,
        },
        -- Active Skills.dat
        cast_time = {
            name = i18n.args.cast_time,
            field = 'cast_time',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        gem_description = {
            name = i18n.args.gem_description,
            field = 'gem_description',
            type = 'Text',
            func = nil,
        },
        active_skill_name = {
            name = i18n.args.active_skill_name,
            field = 'active_skill_name',
            type = 'String',
            func = nil,
        },
        item_class_restriction = {
            name = i18n.args.item_class_restriction,
            field = 'item_class_restriction',
            type = 'List (,) of String',
            func = function(tpl_args, frame, value)
                if value == nil then 
                    return nil
                end
                value = m_util.string.split(value, ', ')
                for _, v in ipairs(value) do
                    local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
                    if result == nil then
                        error(string.format('Invalid item class: %s', v))
                    end
                end
                return value
            end,
        },
        -- Projectiles.dat - manually mapped to the skills
        projectile_speed = {
            name = i18n.args.projectile_speed,
            field = 'projectile_speed',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        -- Misc data derieved from stats
        stat_text = {
            name = i18n.args.stat_text,
            field = 'stat_text',
            type = 'Text',
            func = nil,
        },
        quality_stat_text = {
            name = i18n.args.quality_stat_text,
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
        },
        -- Misc data currently not from game data
        has_percentage_mana_cost = {
            name = i18n.args.has_percentage_mana_cost,
            field = 'has_percentage_mana_cost',
            type = 'Boolean',
            func = data.cast.wrap(m_util.cast.boolean),
        },
        has_reservation_mana_cost = {
            name = i18n.args.has_reservation_mana_cost,
            field = 'has_reservation_mana_cost',
            type = 'Boolean',
            func = data.cast.wrap(m_util.cast.boolean),
        },
        radius = {
            name = i18n.args.radius,
            field = 'radius',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_description = {
            name = i18n.args.radius_description,
            field = 'radius_description',
            type = 'Text',
            func = nil,
        },
        radius_secondary = {
            name = i18n.args.radius_secondary,
            field = 'radius_secondary',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_secondary_description = {
            name = i18n.args.radius_secondary_description,
            field = 'radius_secondary_description',
            type = 'Text',
            func = nil,
        },
        -- not sure if any skill actually has 3 radius componets
        radius_tertiary = {
            name = i18n.args.radius_tertiary,
            field = 'radius_tertiary',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
        },
        radius_tertiary_description = {
            name = i18n.args.radius_tertiary_description,
            field = 'radius_tertiary_description',
            type = 'Text',
            func = nil,
        },
        -- Set manually
        max_level = {
            field = 'max_level',
            type = 'Integer',
        },
    },
}

map.progression = {
    table = 'skill_levels',
    fields = {
        level = {
            name = nil,
            field = 'level',
            type = 'Integer',
            func = nil,
            header = nil,
        },
        level_requirement = {
            name = i18n.args.level_requirement,
            field = 'level_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
        },
        dexterity_requirement = {
            name = i18n.args.dexterity_requirement,
            field = 'dexterity_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
        },
        strength_requirement = {
            name = i18n.args.strength_requirement,
            field = 'strength_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
        },
        intelligence_requirement = {
            name = i18n.args.intelligence_requirement,
            field = 'intelligence_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
        },
        mana_multiplier = {
            name = i18n.args.mana_multiplier,
            field = 'mana_multiplier',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Mana<br>Multiplier',
            fmt = '%s%%',
        },
        critical_strike_chance = {
            name = i18n.args.critical_strike_chance,
            field = 'critical_strike_chance',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Critical<br>Strike<br>Chance',
            fmt = '%s%%',
        },
        mana_cost = {
            name = i18n.args.mana_cost,
            field = 'mana_cost',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = function (skill_data)
                if skill_data["Has reservation mana cost"] then
                    return 'Mana<br>Reserved'
                else
                    return 'Mana<br>Cost'
                end
            end,
            fmt = function (tpl_args, frame, value)
                local str
                if tpl_args.has_percentage_mana_cost then
                    str = '%s%%'
                else
                    str = '%s'
                end
            
                return string.format(str, value)
            end,
        },
        damage_effectiveness = {
            name = i18n.args.damage_effectiveness,
            field = 'damage_effectiveness',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Damage<br>Effectiveness',
            fmt = '%s%%',
        },
        stored_uses = {
            name = i18n.args.stored_uses,
            field = 'stored_uses',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Stored<br>Uses',
        },
        cooldown = {
            name = i18n.args.cooldown,
            field = 'cooldown',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Cooldown',
            fmt = '%ss',
        },
        vaal_souls_requirement = {
            name = i18n.args.vaal_souls_requirement,
            field = 'vaal_souls_requirement',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Vaal<br>souls',
        },
        vaal_stored_uses = {
            name = i18n.args.vaal_stored_uses,
            field = 'vaal_stored_uses',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            header = 'Stored<br>Uses',
        },
        damage_multiplier = {
            name = i18n.args.damage_multiplier,
            field = 'damage_multiplier',
            type = 'Float',
            func = data.cast.wrap(m_util.cast.number),
            header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
            fmt = '%s%%',
        },
        -- from gem experience, optional
        experience = {
            name = i18n.args.experience,
            field = 'experience',
            type = 'Integer',
            func = data.cast.wrap(m_util.cast.number),
            hide = true,
        },
        stat_text = {
            name = i18n.args.stat_text,
            field = 'stat_text',
            type = 'Text',
            func = nil,
            hide = true,
        },
        quality_stat_text = {
            name = i18n.args.quality_stat_text,
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
            hide = true,
        },
    }
}

map.skill_stats_per_level = {
    table = 'skill_stats_per_level',
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        id = {
            field = 'id',
            type = 'String',
        },
        value = {
            field = 'value',
            type = 'Integer',
        },
        is_quality_stat = {
            field = 'is_quality_stat',
            type = 'Boolean',
        },
    },
}

-- 
-- Helper functions 
--
local h = {}

function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
    for key, row in pairs(map.fields) do
        if row.name then
            local val = tpl_args[prefix_in .. row.name]
            if row.func ~= nil then
                val = row.func(tpl_args, frame, val)
            end
            if val ~= nil then    
                if level ~= nil then
                    tpl_args.skill_levels[level][key] = val
                    -- Nuke variables since they're remapped to skill_levels
                    tpl_args[prefix_in .. row.name] = nil
                else
                    tpl_args[row.name] = val
                end
                properties[row.field] = val
            end
        end
    end
end

function h.stats(tpl_args, frame, prefix_in, level)
    for _, stat_type in ipairs(map.stats) do
        local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
        tpl_args.skill_levels[level][stat_type.out] = {}
        for i=1, 8 do
            local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
            local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
            local stat = {
                id = tpl_args[stat_id_key],
                value = tonumber(tpl_args[stat_val_key]),
            }
            if stat.id ~= nil and stat.value ~= nil then
                tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
                
                m_util.cargo.store(frame, {
                    _table = map.skill_stats_per_level.table,
                    [map.skill_stats_per_level.fields.level.field] = level,
                    [map.skill_stats_per_level.fields.id.field] = stat.id,
                    [map.skill_stats_per_level.fields.value.field] = stat.value,
                    [map.skill_stats_per_level.fields.is_quality_stat.field] = stat_type.quality,
                })
                
                -- Nuke variables since they're remapped to skill levels
                tpl_args[stat_id_key] = nil
                tpl_args[stat_val_key] = nil
            end
        end
    end
end

function h.na(tr)
    tr
        :tag('td')
            :attr('class', 'table-na')
            :wikitext('N/A')
            :done()
end

function h.int_value_or_na(tpl_args, frame, tr, value, pdata)
    value = tonumber(value)
    if value == nil then
        h.na(tr)
    else
        value = mwlanguage:formatNum(value)
        if pdata.fmt ~= nil then
            if type(pdata.fmt) == 'string' then
                value = string.format(pdata.fmt, value)
            elseif type(pdata.fmt) == 'function' then
                value = pdata.fmt(tpl_args, frame, value)
            end
        end
        tr
            :tag('td')
                :wikitext(value)
                :done()
    end
end

--
-- For Template:Skill
--
p.table_skills = m_util.cargo.declare_factory{data=map.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=skill_stats_per_level}

function p.skill(frame, tpl_args)
    if tpl_args == nil then
        tpl_args = getArgs(frame, {
            parentFirst = true
        })
    end
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Args
    --
    local properties
    
    tpl_args.skill_levels = {
        [0] = {},
    }
    
    -- Handle level progression
    for i=1, 30 do
        local prefix = i18n.args.prefix_level .. i 
        if m_util.cast.boolean(tpl_args[prefix]) == true then
            -- Don't need this anymore
            tpl_args[prefix] = nil
            tpl_args.skill_levels[i] = {}
            prefix = prefix .. '_'
            local prefix_out = string.format('level%s_', i)
            
            properties = {
                _table = map.progression.table,
                [map.progression.fields.level.field] = i
            }
            h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i)
            m_util.cargo.store(frame, properties)
            
            h.stats(tpl_args, frame, prefix, i)
            
            if tpl_args[prefix .. 'experience'] ~= nil then
                tpl_args.max_level = i
            end
        end
    end
    
    -- Handle static arguments
    properties = {
        _table = map.static.table,
        [map.static.fields.max_level.field] = tpl_args.max_level
    }
    
    h.map_to_arg(tpl_args, frame, properties, '', map.static)
    h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
    h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
    
    m_util.cargo.store(frame, properties)
end

function p.progression(frame)
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    tpl_args.stat_format = {}
    local prefix
    for i=1, 9 do
        prefix = 'c' .. i .. '_' 
        local statfmt = {
            header = tpl_args[prefix .. 'header'],
            abbr = tpl_args[prefix .. 'abbr'],
            pattern_extract = tpl_args[prefix .. 'pattern_extract'],
            pattern_value = tpl_args[prefix .. 'pattern_value'],
            counter = 0,
        }
        if m_util.table.has_all_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
            break
        end
        
        if m_util.table.has_one_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
            error(string.format('All formatting keys must be specified for index "%s"', i))
        end
        
        statfmt.header = m_util.html.abbr(statfmt.abbr, statfmt.header)
        statfmt.abbr = nil
        tpl_args.stat_format[#tpl_args.stat_format+1] = statfmt
    end
    
    
    local result
    local query
    local page 
    local skill_data
    if tpl_args.skill_id then
        query = {}
        query[#query+1] = string.format('[[Is skill id::%s]]', tpl_args.skill_id)
        query[#query+1] = '?Has reservation mana cost#-'
        query['limit'] = 1
        result = m_util.smw.query(query, frame)
        if #result == 0 or #result[1] == 0 then
            error('Couldn\'t find a page for the specified skill id')
        end
        page = result[1][1]
        skill_data = result[1]
    else
        if tpl_args.page then
            page = tpl_args.page
        else
            page = mw.title.getCurrentTitle().prefixedText
        end
        query = {}
        query[#query+1] = string.format('[[%s]]', page)
        query[#query+1] = '?Has reservation mana cost#-'
        
        result = m_util.smw.query(query, frame)
        if #result == 0 then
            error('Couldn\'t find the queried data on the skill page')
        end
        
        skill_data = result[1]
    end
    
    skill_data["Has reservation mana cost"] = data.cast.wrap(m_util.cast.boolean)(skill_data["Has reservation mana cost"])
    
    query = {}
    query[#query+1] = string.format('[[-Has subobject::%s]]', page)
    for _, pdata in ipairs(map.progression) do
        query[#query+1] = '?' .. pdata.property .. '#-'
    end
    query[#query+1] = '?Is skill level#-'
    query[#query+1] = '?Has stat text'
    query[#query+1] = '?Has stat id'
    query[#query+1] = '?Has stat value#-'
    query[#query+1] = '?Has quality stat id'
    query[#query+1] = '?Has quality stat value#-'
    
    query.sort = 'Is skill level'
    
    result = m_util.smw.query(query, frame)
    
    if #result == 0 then
        error('No gem level progression data found')
    end
    
    headers = {}
    for i, row in ipairs(result) do
        for k, v in pairs(row) do
            if v ~= "" then
                headers[k] = true
            end
        end
    end
    
    
    
    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable skill-progression-table')
    
    local head = tbl:tag('tr')
    head
        :tag('th')
            :wikitext('Level')
            :done()
    
    for _, pdata in ipairs(map.progression) do
        -- TODO should be nil?
        if pdata.hide == nil and headers[pdata.property] then
            local text
            if type(pdata.header) == 'function' then
                text = pdata.header(skill_data)
            else
                text = pdata.header
            end
            head
                :tag('th')
                    :wikitext(text)
                    :done()
        end
    end
    
    for _, statfmt in ipairs(tpl_args.stat_format) do
        head
            :tag('th')
                :wikitext(statfmt.header)
                :done()
    end
    
    if headers['Has experience requirement'] then
        head
            :tag('th')
                :wikitext(m_util.html.abbr('Exp.', 'Experience Needed to Level Up'))
                :done()
            :tag('th')
                :wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed'))
                :done()
    end
    
    local tblrow
    local lastexp = 0
    local experience
    
    for i, row in ipairs(result) do
        tblrow = tbl:tag('tr')
        tblrow
            :tag('th')
                :wikitext(row['Is skill level'])
                :done()
        
        for _, pdata in ipairs(map.progression) do
            if pdata.hide == nil and headers[pdata.property] then
                h.int_value_or_na(tpl_args, frame, tblrow, row[pdata.property], pdata)
            end
        end
        
        -- stats
        stats = m_util.string.split(row['Has stat text'], '<br>')
        for _, statfmt in ipairs(tpl_args.stat_format) do
            local match = {}
            for j, stat in ipairs(stats) do
                match = {string.match(stat, statfmt.pattern_extract)}
                if #match > 0 then
                    -- TODO maybe remove stat here to avoid testing against in future loops
                    break
                end
            end
            if #match == 0 then
                h.na(tblrow)
            else
                -- used to find broken progression (i.e. due to game updates)
                statfmt.counter = statfmt.counter + 1
                tblrow
                    :tag('td')
                        :wikitext(string.format(statfmt.pattern_value, match[1], match[2], match[3], match[4], match[5]))
                        :done()
            end
        end
        
        -- TODO: Quality stats, afaik no gems use this atm
        
        if headers['Has experience requirement'] then
            experience = tonumber(row['Has experience requirement'])
            if experience ~= nil then
                h.int_value_or_na(tpl_args, frame, tblrow, experience - lastexp, {})
                
                lastexp = experience
            else
                h.na(tblrow)
            end
            h.int_value_or_na(tpl_args, frame, tblrow, experience, {})
        end
    end
    
    local empty = ''
    
    for _, statfmt in ipairs(tpl_args.stat_format) do
        if statfmt.counter == 0 then
            empty = '[[Category:Pages with broken skill progression tables]]'
            break
        end
    end
    
    return empty .. tostring(tbl)
end

function p.map(arg)
    for key, data in pairs(map[arg].fields) do
        mw.logObject(key)
    end
end

return p