Module:Damage: Difference between revisions

no edit summary
No edit summary
No edit summary
 
(44 intermediate revisions by 2 users not shown)
Line 1: Line 1:
-- pystart
-- pystart
require('Module:CommonFunctions');
require('Module:CommonFunctions')
local i18n = require('Module:I18n')
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local inspect = require('Module:Inspect').inspect
local inspect = require('Module:Inspect').inspect
local getTranslations = i18n.getTranslations
local p = {}
local p = {}


Line 8: Line 10:
function p.main(frame)
function p.main(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
    local tr = getTranslations(frame, 'Template:Damage', args.lang, true)
     local out
     local out
    function translate(key)
        return i18n.translate(tr, key)
    end


     function inArgs(key)
     function inArgs(key)
Line 27: Line 34:
         for _, mode in ipairs(modes) do
         for _, mode in ipairs(modes) do
             func(mode)
             func(mode)
        end
    end
    function forEachDamageType(func)
        for _, damage_type in ipairs({ 'min', 'max' }) do
            func(damage_type)
         end
         end
     end
     end
Line 45: Line 58:
         do_table = args[1] == 'true',
         do_table = args[1] == 'true',
         character = args[2] or args.char or 'Elsword',
         character = args[2] or args.char or 'Elsword',
        lang_suffix = args.lang and ('/' .. args.lang) or '',
        lang_append = args.lang ~= nil and args.lang ~= '',
         format = args.format ~= 'false',
         format = args.format ~= 'false',
         no_max = args.no_max == 'true',
         no_max = args.no_max == 'true',
Line 85: Line 100:
         {
         {
             key = '',
             key = '',
             name = 'Normal',
             name = translate('Normal'),
             value = 1
             value = 1
         },
         },
         {
         {
             key = 'enhanced',
             key = 'enhanced',
             name = 'Enhanced (Trait)',
             name = translate('Enhanced (Trait)'),
             value = args.enhanced ~= nil and 0.8
             value = args.enhanced ~= nil and 0.8
         },
         },
         {
         {
             key = 'empowered',
             key = 'empowered',
             name = 'Empowered',
             name = translate('Empowered'),
             value = args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false
             value = args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false
         },
         },
         {
         {
             key = 'useful',
             key = 'useful',
             name = 'Useful',
             name = translate('Useful'),
             value = (args.hits_useful or args.avg_hits_useful) and (args.useful_penalty or args.useful or 0.8) or false
             value = (args.hits_useful or args.avg_hits_useful) and (args.useful_penalty or args.useful or 0.8) or false
         },
         },
         {
         {
             key = 'heavy',
             key = 'heavy',
             name = 'Heavy',
             name = translate('Heavy'),
             value = args.heavy ~= nil and 1.44
             value = args.heavy ~= nil and 1.44
         }
         }
Line 126: Line 141:
             --]]
             --]]
             local passive_name = v
             local passive_name = v
            local passive_title = v .. OPTIONS.lang_suffix
             local is_custom = string.find(k, '_define') ~= nil
             local is_custom = string.find(k, '_define') ~= nil
             local passive_index = string.match(k, "%d")
             local passive_index = string.match(k, "%d")
             local passive_values = split(is_custom and v or
             local passive_values = split(is_custom and v or
                 frame:preprocess('{{:' .. passive_name .. '}}{{#arrayprint:' .. passive_name .. '}}'));
                 frame:preprocess('{{:' .. passive_name .. '}}{{#arrayprint:' .. passive_name .. '}}'));
            local display_title


             if is_custom then
             if is_custom then
                 passive_name = passive_values[#passive_values]
                 passive_name = passive_values[#passive_values]
                 passive_values[#passive_values] = nil
                 passive_values[#passive_values] = nil
            elseif OPTIONS.lang_append then
                --[[
                Translate page's display title to passive name.
                Customized will override this name, thus no need to perform the translation
                --]]
                display_title = i18n.getTranslatedTitle(passive_title)
             end
             end


Line 140: Line 163:
                 value = passive_values[1],
                 value = passive_values[1],
                 value_pvp = passive_values[2],
                 value_pvp = passive_values[2],
                 alias = args['alias' .. passive_index] or (passive_index == OPTIONS.append_index and OPTIONS.append_name),
                 alias = args['alias' .. passive_index] or (passive_index == OPTIONS.append_index and OPTIONS.append_name) or display_title,
                 suffix = args['suffix' .. passive_index] and (' ' .. args['suffix' .. passive_index]) or '',
                 suffix = args['suffix' .. passive_index] and (' ' .. args['suffix' .. passive_index]) or '',
                 prefix = args['prefix' .. passive_index] and (args['prefix' .. passive_index] .. ' ') or '',
                 prefix = args['prefix' .. passive_index] and (args['prefix' .. passive_index] .. ' ') or '',
                 exist = frame:preprocess('{{#ifexist:' .. passive_name .. '|true|false}}') == 'true'
                 exist = frame:preprocess('{{#ifexist:' .. passive_name .. '|true|false}}') == 'true'
             }
             }
         elseif not string.find(v, '[a-hj-zA-HJ-Z]+') then
         elseif string.match(v, '^[()+%-*/%d%s,.i]+$') then
             --[[
             --[[
             Change how args are received.
             Change how args are received.
Line 257: Line 280:
     else
     else
         DAMAGE_CONFIG = BASE_DAMAGE_CONFIG
         DAMAGE_CONFIG = BASE_DAMAGE_CONFIG
    end
    -- Helper function to check if a table is not empty
    local function isTableNotEmpty(tbl)
        return next(tbl) ~= nil
    end
    -- Function to apply inheritance for a specific damage type and argument
    local function applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritValue)
        if mainArgValue == '' then
            return inheritValue
        elseif mainArgValue and string.find(mainArgValue, 'i') and inheritValue then
            return eval(mainArgValue:gsub('i', inheritValue))
        end
        return mainArgValue
    end
    -- Function to apply inheritance for a specific argument key
    local function applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType)
        local mainKey = argTable[1] .. damageType
        local mainKeyPrefixed = prefix .. mainKey
        local mainArgValues = args[mainKeyPrefixed]
        if mainArgValues then
            local i = 1
            local cancelDmgLen = args.cancel_dmg and #args.cancel_dmg or 0
            while i <= (#args.dmg + cancelDmgLen) do
                local mainArgValue = mainArgValues[i]
                for ix, inheritKey in ipairs(argTable) do
                    local inheritArg = args[prefix .. inheritKey .. damageType] or args[inheritKey .. damageType]
                    -- Basic damage/hits inheritance request detected. Ignore min/max.
                    if damageType and mainKey:gsub(damageType, "") == argTable[#argTable] then
                        inheritArg = args[prefix .. inheritKey] or args[inheritKey]
                    end
                    if inheritArg and inheritArg[i] and
                        (damageTypeIndex == 1 and ix ~= 1 or damageTypeIndex ~= 1) and tonumber(inheritArg[i])
                    then
                        mainArgValues[i] = applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritArg[i])
                        break
                    end
                end
                i = i + 1
            end
        end
     end
     end


Line 262: Line 334:
     function inherit(mode)
     function inherit(mode)
         local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
         local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
        for config_key, config_value in pairs(DAMAGE_CONFIG) do
            for arg_table_key, arg_table in pairs(config_value) do
                if arg_table_key ~= 'provided' and arg_table then
                    -- We only do this for the first (main) key
                    local main_key = arg_table[1]
                    local main_key_prefixed = prefix .. main_key
                    local main_arg_values = args[main_key_prefixed]
                    -- Only if the main argument values exist.
                    if main_arg_values then
                        local i = 1
                        --[[
                            Loop over all damage and attempt to inherit in chain.
                            Break the loop if a match was found. Note: For this to work, the value must be an empty string.
                            Alternatively, it can contain an "i" to template the value to inherit.
                        ]]
                        local cancel_dmg_len = args.cancel_dmg and #(args.cancel_dmg) or 0
                        while i <= (#(args.dmg) + cancel_dmg_len) do
                            local main_arg_value = main_arg_values[i]


                            for ix, inherit_key in ipairs(arg_table) do
        for configKey, configValue in pairs(DAMAGE_CONFIG) do
                                local inherit_arg = args[prefix .. inherit_key] or args[inherit_key]
            for argTableKey, argTable in pairs(configValue) do
                                -- No inheritance from itself.
                if argTableKey ~= 'provided' and isTableNotEmpty(argTable) then
                                if inherit_arg and inherit_arg[i] and inherit_arg[i] ~= '' and ix ~= 1 then
                    for damageTypeIndex, damageType in ipairs({ '', '_min', '_max' }) do
                                    -- Only inherit if empty
                        applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType)
                                    if main_arg_value == '' then
                                        args[main_key_prefixed][i] = inherit_arg[i]
                                        break
                                    elseif main_arg_value and string.find(main_arg_value, 'i') and inherit_arg[i] then
                                        args[main_key_prefixed][i] = eval(main_arg_value:gsub('i', inherit_arg[i]))
                                        break
                                    end
                                end
                            end
 
                            i = i + 1
                        end
                     end
                     end
                 end
                 end
Line 312: Line 353:
         for config_key, config_value in pairs(DAMAGE_CONFIG) do
         for config_key, config_value in pairs(DAMAGE_CONFIG) do
             for k, v in pairs(config_value) do
             for k, v in pairs(config_value) do
                 local output_value = v
                 local output_value = {}
                 for _, v2 in ipairs(v) do
 
                     local arg_from_template = args[prefix .. v2] or args[v2]
                -- When both min and max are found, we need to break from the loop.
                    if arg_from_template ~= nil then
                local isValueFound = { min = false, max = false }
                        output_value = arg_from_template
 
                        if k == 'provided' then
                 for _, v2 in ipairs(v) do -- This array holds the argument names with fallbacks
                            output_value = true
                     forEachDamageType(function(damage_type)
                            -- Do not generate total_damage values at all if the skill can't reach them.
                        -- If there already is a value for this damage type (min or max), do not continue.
                            if string.find(config_key, 'total_') and OPTIONS.no_max then
                        if isValueFound[damage_type] == true then
                            return
                        end
 
                        local arg_from_template =
                            args[prefix .. v2 .. '_' .. damage_type]
                            or args[v2 .. '_' .. damage_type]
                            or args[prefix .. v2]
                            or args[v2];
 
                        if arg_from_template ~= nil then
                            if k == 'provided' then
                                output_value = true
                                -- Do not generate total_damage values at all if the skill can't reach them.
                                if string.find(config_key, 'total_') and OPTIONS.no_max then
                                    output_value = false
                                end
                            else
                                if type(output_value) ~= "table" then
                                    output_value = {}
                                end
                                output_value[damage_type] = arg_from_template
                            end
                            -- Mark the value as found.
                            isValueFound[damage_type] = true
                        else
                            if k == 'provided' then
                                 output_value = false
                                 output_value = false
                            else
                                output_value[damage_type] = {}
                             end
                             end
                         end
                         end
                    end)
                    -- Both values found, we can now break the loop.
                    if isValueFound.min and isValueFound.max then
                         break
                         break
                    else
                        if k == 'provided' then
                            output_value = false
                        else
                            output_value = {}
                        end
                     end
                     end
                 end
                 end
Line 351: Line 418:
                     local new_value = table.deep_copy(damage_value)
                     local new_value = table.deep_copy(damage_value)


                     for k, hit_count in ipairs(new_value.hit_counts) do
                     forEachDamageType(function(damage_type)
                        hit_count = hit_count == '' and 1 or hit_count
                        for k, hit_count in ipairs(new_value.hit_counts[damage_type]) do
                        new_value.hit_counts[k] = hit_count *
                            hit_count = hit_count == '' and 1 or hit_count
                            ((string.find(damage_key, 'awk_') and args.awk_count) and args.awk_count[1] or args.count[1])
                            new_value.hit_counts[damage_type][k] = hit_count *
                     end
                                ((string.find(damage_key, 'awk') and args.awk_count) and args.awk_count[1] or args.count[1])
                        end
                     end)


                     WITH_EACH[mode][damage_key:gsub("total_", "each_")] = damage_value
                     WITH_EACH[mode][damage_key:gsub("total_", "each_")] = damage_value
Line 372: Line 441:
         for mode, mode_content in pairs(DAMAGE_PARSED) do
         for mode, mode_content in pairs(DAMAGE_PARSED) do
             for damage_key, damage_value in pairs(mode_content) do
             for damage_key, damage_value in pairs(mode_content) do
                 local i = 1
                 forEachDamageType(function(damage_type)
                local output = 0
                    local i = 1
                -- Check if to even generate the damage.
                    local output = 0
                if damage_value.provided then
                    -- Check if to even generate the damage.
                    -- Loop through damage numbers and multiply them with hits.
                    if damage_value.provided then
                    for k, damage_number in ipairs(damage_value.damage_numbers) do
                        -- Loop through damage numbers and multiply them with hits.
                        local hit_count = damage_value.hit_counts[i]
                        for k, damage_number in ipairs(damage_value.damage_numbers[damage_type]) do
                        hit_count = hit_count == '' and 1 or hit_count
                            local hit_count = damage_value.hit_counts[damage_type][i]
                        output = output + (damage_number * hit_count)
                            hit_count = hit_count == '' and 1 or hit_count
                        i = i + 1
                            output = output + (damage_number * hit_count)
                            i = i + 1
                        end
                        -- Write the result to a separate object.
                        if not BASIC_DAMAGE[mode][damage_key] then
                            BASIC_DAMAGE[mode][damage_key] = {}
                        end
                        BASIC_DAMAGE[mode][damage_key][damage_type] = output
                     end
                     end
                    -- Write the result to a separate object.
                 end)
                    BASIC_DAMAGE[mode][damage_key] = output
                 end
             end
             end
         end
         end
Line 397: Line 471:
             for damage_key, damage_value in pairs(mode_content) do
             for damage_key, damage_value in pairs(mode_content) do
                 local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key]
                 local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key]
                 if not string.find(damage_key, 'cancel_') and cancel_candidate then
                 forEachDamageType(function(damage_type)
                    BASIC_DAMAGE[mode][damage_key] = damage_value + cancel_candidate
                    if not string.find(damage_key, 'cancel_') and cancel_candidate then
                 end
                        BASIC_DAMAGE[mode][damage_key][damage_type] = damage_value[damage_type] +
                            cancel_candidate[damage_type]
                    end
                 end)
             end
             end
         end
         end
Line 421: Line 498:
                     --]]
                     --]]
                     if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then
                     if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then
                         WITH_TRAITS[mode][damage_key .. ((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key))] =
                         forEachDamageType(function(damage_type)
                             damage_value * trait.value
                            local new_key = damage_key ..
                                ((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key));
                            if not WITH_TRAITS[mode][new_key] then
                                WITH_TRAITS[mode][new_key] = {}
                            end
                             WITH_TRAITS[mode][new_key][damage_type] = damage_value[damage_type] * trait.value
                        end)
                     end
                     end
                 end
                 end
Line 441: Line 524:
         for mode, mode_content in pairs(WITH_TRAITS) do
         for mode, mode_content in pairs(WITH_TRAITS) do
             for damage_key, damage_value in pairs(mode_content) do
             for damage_key, damage_value in pairs(mode_content) do
                 local combinations = { {} }
                 forEachDamageType(function(damage_type)
                for passive_key, passive in pairs(PASSIVES) do
                    local combinations = { {} }
                    local count = #combinations
                    for passive_key, passive in pairs(PASSIVES) do
                    for i = 1, count do
                        local count = #combinations
                        local new_combination = { unpack(combinations[i]) }
                        for i = 1, count do
                        table.insert(new_combination, passive_key)
                            local new_combination = { unpack(combinations[i]) }
                        table.insert(combinations, new_combination)
                            table.insert(new_combination, passive_key)
                            table.insert(combinations, new_combination)
                        end
                     end
                     end
                end
                    for _, combination in pairs(combinations) do
                for _, combination in pairs(combinations) do
                        local passive_multiplier = 1
                    local passive_multiplier = 1
                        local name_suffix = ''
                    local name_suffix = ''
                        if #combination > 0 then
                    if #combination > 0 then
                            table.sort(combination)
                        table.sort(combination)
                            for _, passive_key in pairs(combination) do
                        for _, passive_key in pairs(combination) do
                                passive_multiplier = passive_multiplier *
                            passive_multiplier = passive_multiplier *
                                    tonumber(PASSIVES[passive_key][mode == 'PvE' and 'value' or 'value_pvp'])
                                tonumber(PASSIVES[passive_key][mode == 'PvE' and 'value' or 'value_pvp'])
                                name_suffix = name_suffix .. '_passive' .. passive_key
                            name_suffix = name_suffix .. '_passive' .. passive_key
                            end
                        end
                        local new_damage_key = damage_key .. name_suffix;
                        if not WITH_PASSIVES[mode][new_damage_key] then
                            WITH_PASSIVES[mode][new_damage_key] = {}
                         end
                         end
                        WITH_PASSIVES[mode][new_damage_key][damage_type] = damage_value[damage_type] * passive_multiplier
                     end
                     end
                    WITH_PASSIVES[mode][damage_key .. name_suffix] = damage_value * passive_multiplier
                 end)
                 end
             end
             end
         end
         end
Line 470: Line 559:


     local RANGE = {
     local RANGE = {
         min_count = args.range_min_count and args.range_min_count[1] or 1,
         min_count = args.range_min_count and args.range_min_count[1],
         max_count = args.range_max_count and args.range_max_count[1] or 1,
         max_count = args.range_max_count and args.range_max_count[1],
         PvE = {
         PvE = {
             min = args.range_min and args.range_min[1] or 1,
             min = args.range_min and args.range_min[1],
             max = args.range_max and args.range_max[1] or 1
             max = args.range_max and args.range_max[1]
         },
         },
         PvP = {
         PvP = {
             min = args.range_min and (args.range_min[2] or args.range_min[1]) or 1,
             min = args.range_min and (args.range_min[2] or args.range_min[1]),
             max = args.range_max and (args.range_max[2] or args.range_max[1]) or 1
             max = args.range_max and (args.range_max[2] or args.range_max[1])
         }
         }
     }
     }
Line 488: Line 577:
             for damage_key, damage_value in pairs(mode_content) do
             for damage_key, damage_value in pairs(mode_content) do
                 WITH_RANGE[mode][damage_key] = { min = 0, max = 0 }
                 WITH_RANGE[mode][damage_key] = { min = 0, max = 0 }
                 for _, range in ipairs({ 'min', 'max' }) do
                 forEachDamageType(function(damage_type)
                     local final_damage_value = damage_value * (1 + ((RANGE[mode][range] - 1) * RANGE[range .. '_count'])) *
                    local range_count = RANGE[damage_type .. '_count'] or 1;
                        OPTIONS.perm_buff[mode]
                    -- If min count preset, use range_max for the multiplier.
                     WITH_RANGE[mode][damage_key][range] = not OPTIONS.format and final_damage_value or
                    local range_multiplier = RANGE[mode][damage_type] or (damage_type == 'min' and RANGE.min_count and RANGE[mode].max) or 1;
                     local final_range_multiplier = (1 + ((range_multiplier - 1) * range_count));
                    local perm_buff = OPTIONS.perm_buff[mode];
 
                    local final_damage_value = damage_value[damage_type] * final_range_multiplier * perm_buff;
                     WITH_RANGE[mode][damage_key][damage_type] = not OPTIONS.format and final_damage_value or
                         formatDamage(final_damage_value)
                         formatDamage(final_damage_value)
                 end
                 end)
             end
             end
         end
         end
Line 559: Line 653:
         {
         {
             type = 'extra',
             type = 'extra',
             text = { 'Average' },
             text = { translate('Average') },
             is_visible = OPTIONS.no_max,
             is_visible = OPTIONS.no_max,
             no_damage = true
             no_damage = true
Line 566: Line 660:
             type = 'passives',
             type = 'passives',
             text = checkPassives({
             text = checkPassives({
                 output = { 'Base' },
                 output = { translate('Base') },
                 action = function(passive, output)
                 action = function(passive, output)
                     if passive.is_combined then
                     if passive.is_combined then
Line 598: Line 692:
             type = 'passive_appended',
             type = 'passive_appended',
             text = {
             text = {
                 'Normal',
                 translate('Normal'),
                 OPTIONS.is_append and
                 OPTIONS.is_append and
                 link(PASSIVES[OPTIONS.append_index].name,
                 link(PASSIVES[OPTIONS.append_index].name,
Line 612: Line 706:
         {
         {
             type = 'awakening',
             type = 'awakening',
             text = { 'Regular', (function()
             text = { translate('Regular'), (function()
                 if OPTIONS.dmp then
                 if OPTIONS.dmp then
                     return link('Dynamo Point System', 'Dynamo Configuration', nil,
                     return link('Dynamo Point System' .. OPTIONS.lang_suffix, 'Dynamo Configuration', args.awk_prefix,
                        OPTIONS.dmp ~= 'false' and ('(' .. OPTIONS.dmp .. ' DMP)'))
                    OPTIONS.dmp ~= 'false' and (fillTemplate('({1} DMP)', { OPTIONS.dmp })) .. (args.awk_suffix and (' ' .. args.awk_suffix) or ''))
                 elseif args.awk_alias then
                 elseif args.awk_alias then
                     return link(unpack(args.awk_alias))
                     return link(args.awk_alias[1], args.awk_alias[2], args.awk_prefix, args.awk_suffix)
                 end
                 end
                 return link('Awakening Mode')
                 return link('Awakening Mode' .. OPTIONS.lang_suffix, translate('Awakening Mode'), args.awk_prefix, args.awk_suffix)
             end)()
             end)()
             },
             },
Line 629: Line 723:
             type = 'traits',
             type = 'traits',
             text = checkTraits({
             text = checkTraits({
                 output = { 'Normal' },
                 output = { translate('Normal') },
                 action = function(trait, output)
                 action = function(trait, output)
                     table.insert(output, trait.name)
                     table.insert(output, trait.name)
Line 644: Line 738:
             type = 'cancel',
             type = 'cancel',
             text = {
             text = {
                 'Cancel', 'Full'
                 translate('Cancel'),
                translate('Full'),
             },
             },
             keywords = { 'cancel' },
             keywords = { 'cancel' },
Line 654: Line 749:
             text = {
             text = {
                 (inArgs('count') and not OPTIONS.use_avg) and
                 (inArgs('count') and not OPTIONS.use_avg) and
                 (table.concat({ 'Per', args.count_name or 'Group' }, ' ')) or 'Average',
                 (fillTemplate(translate('Per {1}'), { args.count_name or translate('Group') })) or
                 'Max'
                translate('Average'),
                 translate('Max')
             },
             },
             keywords = (function()
             keywords = (function()
Line 737: Line 833:


     function doInitialCell(new_row)
     function doInitialCell(new_row)
         return new_row:tag('th'):wikitext('Mode')
         return new_row:tag('th'):wikitext(translate('Mode'))
     end
     end


Line 796: Line 892:
     function doContentByMode(mode)
     function doContentByMode(mode)
         local mode_row = TABLE:new()
         local mode_row = TABLE:new()
         mode_row:tag('td'):wikitext(frame:expandTemplate { title = mode })
         mode_row:tag('td'):wikitext(frame:expandTemplate { title = translate(mode) })
         local damage_entries = returnDamageInOrder()
         local damage_entries = returnDamageInOrder()
         local last_number
         local last_number
Line 830: Line 926:
     end
     end


     doTable()
     if OPTIONS.do_table then
        doTable()
    end


     -- Dump all values if wanted.
     -- Dump all values if wanted.
Line 844: Line 942:
     if OPTIONS.bug then
     if OPTIONS.bug then
         bug = frame:expandTemplate {
         bug = frame:expandTemplate {
             title = 'SkillText',
             title = translate('SkillText'),
             args = { 'FreeTraining' }
             args = { 'FreeTraining' }
         }
         }