Documentation for this module may be created at Module:Herobox/doc
-- <nowiki>
-- This module contains some helper functions for the design Template:Herobox
--
local p = {}
local hvData = mw.loadData('Module:Hv/data')
libTraits = require('Module:LibTraits')
-- gets all traits or abilities of the given hero and calculates the highest ranked ability or
-- trait, ignores the lower ranks.
-- arg1: given heroId (will be lower case'd)
-- arg2: flag to choose: either 'traits' or 'abilities'
-- debug console: =p.listFullyAscended({args={'lord zomm', 'traits'}})
-- output > from the grave-iii/lord of the undead/slow/soulless scourge/mystical-iii
function p.listFullyAscended(frame)
local heroId = frame.args[1]
local attrTraitsOrAbilities = frame.args[2]
-- check args
if heroId == nil then
return "ERROR: arg1 missing, expected heroId"
end
heroId = string.lower(heroId)
local heroData = hvData[heroId]
if heroData == nil then
return 'ERROR: arg1: unknown heroId ' .. heroId
end
if attrTraitsOrAbilities ~= "traits" and attrTraitsOrAbilities ~= "abilities" then
return 'ERROR: arg2 missing or invalid, expected "traits" or "abilities"'
end
local rankToNumber = { ['i'] = 1, ['ii'] = 2, ['iii'] = 3, ['iv'] = 4, ['v'] = 5 }
local rankToSign = { 'i', 'ii', 'iii', 'iv', 'v' }
local highestRanks = {} -- trait/ability with highest rank
local byAscensions = {} -- trait/ability by lowest Ascension, needed for sort order
local traitsOrAbilities = heroData[attrTraitsOrAbilities]
local upgrades = heroData['upgrade'] or {}
for abRanked, ascensionNo in pairs(traitsOrAbilities) do
local ab, rank = libTraits.splitRank(abRanked)
mw.log(abRanked, '=>', ab, ' + ', rank, ascensionNo)
if rank == "" then -- it's a ability/trait without rank
rank = 0
else
rank = rankToNumber[rank]
end
local currentRank = highestRanks[ab] or -1
if rank > currentRank then
highestRanks[ab] = rank
end
-- a few epic traits (upgrades) might replace non-epic traits
if upgrades[abRanked] ~= nil then
highestRanks[ab] = nil -- remove it because it will be superseded
end
-- save the lowest ascension for the trait/ability. Needed for correct sort order
local currentAsc = byAscensions[ab] or 1000
if ascensionNo < currentAsc then
byAscensions[ab] = ascensionNo
end
end
local sortIndex = {} -- to list traits/abilities in order of the ascensions
for ab, _ in pairs(highestRanks) do
sortIndex[#sortIndex+1] = ab
end
-- sort by ascension
table.sort(sortIndex, function (a, b)
mw.log ('Sorter called', a, "<=>", b);
-- if true then return a<b; end -- this line ABORTs! for debug only, to see errors!
local siA = byAscensions[a]
local siB = byAscensions[b]
if siA == siB then
return a < b
else
return siA < siB
end
end)
local result = {}
for _, ab in ipairs(sortIndex) do
local rank = highestRanks[ab]
mw.log ("add rank: ", ab, '-' ,rank)
if rank > 0 then
result[#result+1] = ab .. '-' .. (rankToSign[rank])
else
result[#result+1] = ab
end
end
return table.concat(result, "/")
end
-- ---------------------------------------------
local abilitiesData = mw.loadData('Module:Abilities/data')
local sortOrderTypes = { ['Basic Attack'] = 10, ['Special Attack'] = 20, ['Trait'] = 30 }
-- helper to prepare the data structure 'v'. It's used to bring traits and abilities with ranks in the correct order, and to display the infos.
-- what (IN): a trait id or ability id with rank (e.g. 'heart stopper-ii'). It will be added to 'result' as key without the rank.
-- ascension (IN): 0 to 2, defines when the hero gets that trait/ability
-- result (OUT): table that saves the result, built by this function. It's a map with key='ascension id without rank' and value='v'. Example:
-- result = {
-- ['heart stopper'] = { -- <== this is a 'v'
-- hasRanks = true,
-- i = 0,
-- ii = 1,
-- iii = 2,
-- numOfRanks = 3,
-- sort = 0
-- },
-- slow = {
-- ascension = 0,
-- hasRanks = false,
-- numOfRanks = 1,
-- sort = 0
-- },
-- ..., ... }
-- resultSortindex (OUT): returned list of keys of 'result'. This index should be sorted by
-- v.sort when finished to have the abilities and traits in correct order
function prepareAbilityData(what, ascension, result, resultSortindex)
mw.log('prepareAbilityData what=', what)
local abtr, rank = libTraits.splitRank(what)
mw.log('prepareAbilityData -->', abtr, rank)
local hasRanks = false
local ability = abilitiesData[abtr]
if ability == nil then
mw.log('ERROR in prepareAbilityData: unknown trait or ability: ' .. (abtr or "nil"))
return
end
if ability.ranks ~= 'N/A' then
hasRanks = true
if rank == '' then
rank = 'i'
end
else
-- key to get ascension for traits/abilities without rank
rank = 'ascension'
end
local v = result[abtr]
local sortIdx = sortOrderTypes[ability.type] + ascension
if v == nil then
v = {}
result[abtr] = v
v.sort = sortIdx
v.hasRanks = hasRanks
v.numOfRanks = 1
resultSortindex[#resultSortindex+1] = abtr
else
-- update order and number of ranks this hero has
if sortIdx < v.sort then
v.sort = sortIdx
end
v.numOfRanks = v.numOfRanks + 1
end
v[rank] = ascension
end
-- helper for wiki markup
function headline(ability, note)
local cost = ability['energy cost']
if tonumber(cost) ~= nil then -- number?
cost = cost .. " turns"
end
if (ability.targets or 0) == 5 then
cost = cost .. ", AoE"
end
return ability.type .. ' (' .. cost .. ')' .. note
end
-- helper: hero: hv[hero]; epic: epics[hero]; asc = 0,1,2 or 3=epic, 4=skin
-- return image name for the hero's token, and description (1st Ascension, etc)
function getImageAndRequirement(hero, ability, epic, asc)
local textReqCondition = { '1st Ascension', '2nd Ascension', 'Epic', 'Skin' }
local filename
local requirement = textReqCondition[asc] or "??"
if 0 <= asc and asc <= 2 then
filename = hero['token ' .. (asc+1)]
elseif asc == 3 then
if (epic ~= nil) then
filename = (epic['name'] or "Blank Ability") .. '.png'
requirement = requirement .. ' "' .. epic['name'] .. '"'
else
filename = 'Blank Ability.png'
requirement = requirement .. " (missing epic info)"
end
elseif asc == 4 then
local skinsData = mw.loadData('Module:Skins/data')
local skinId = ability['skin']
local skin = skinsData[skinId]
if (skin ~= nil) then
filename = skin.token
requirement = requirement .. ' "' .. skin.name .. '"'
else
filename = 'Blank Ability.png'
requirement = requirement .. " (missing info for skin " .. skinId ..")"
end
end
if filename == nil or filename == "" then
filename = "Blank token.png"
end
return filename, requirement
end
-- create a tooltip because Tooltip template is gets not expanded when script is #invoked
function makeTooltip(which, id, textToHover)
return '<span class="' .. which .. '-tooltip" data-' .. which .. '=' .. '"' .. id .. '">' .. textToHover .. '</span>'
end
-- generated Wiki markup for the abilities and traits table of the Herobox
-- =p.abilitiesAndTraitsTable({ args={ 'Lord Zomm' }})
function p.abilitiesAndTraitsTable(frame)
local args = (frame.args[1] ~= nil) and frame.args or frame:getParent().args
local heroId = string.lower(args[1])
local effectsData = mw.loadData('Module:Effects/data')
local epicsData = mw.loadData('Module:Epics/data')
local epic = epicsData[heroId]
local hero = hvData[heroId]
local result = {}
local sortindex = {}
-- prepare Basic attack, Abilities, and Traits -> result + sortindex
prepareAbilityData(hero['basic attack'], 0, result, sortindex)
local abtr = hero.abilities
for ab, ascension in pairs(abtr) do
prepareAbilityData(ab, ascension, result, sortindex)
end
abtr = hero.traits
for trait, ascension in pairs(abtr) do
prepareAbilityData(trait, ascension, result, sortindex)
end
-- add epic traits and traits of Ultimate skins, make them a dummy ascension ('3A' and '4A')
local epicTrait = hero['epic trait']
mw.log("epicTrait", epicTrait)
if epicTrait ~= nil then
prepareAbilityData(epicTrait, 3, result, sortindex)
end
local skinTrait = hero['skin trait']
if skinTrait ~= nil then
prepareAbilityData(skinTrait, 4, result, sortindex)
end
-- sort index by v.sort (=type and ascension)
table.sort(sortindex, function(a,b)
if result[a].sort == result[b].sort then
return a < b
else
return result[a].sort < result[b].sort
end
end)
-- create the wiki table
local markup = {}
local rankPlaceholderIdx = 0
for _, k in pairs(sortindex) do
v = result[k]
markup[#markup+1] = "<br>'''<span style='font-size:larger;'> " .. abilitiesData[k].name
markup[#markup+1] = '' -- placeholder for ranks (I/II..)
rankPlaceholderIdx = #markup
markup[#markup+1] = "</span>'''"
-- <nowiki>
markup[#markup+1] = '[[File:' .. abilitiesData[k].image .. '|left|60px|link=]]\n'
-- </nowiki>
local ability = abilitiesData[k]
if v.hasRanks then -- trait/ability with ranks?
local heroRanks = {}
local abRanks = ability.ranks
local note = ""
if v.numOfRanks > 1 then
note = ", upgrades with Ascensions"
end
mw.log(ability.name, note, ' known ability ranks: ' .. abRanks)
markup[#markup+1] = '{| border="0" \n'
markup[#markup+1] = '|-\n'
markup[#markup+1] = "|'''" .. headline(ability, note).. "'''<br>\n"
-- loop over all ranks of the trait/ability and display those which the hero has
for rank in string.gmatch(abRanks, '([IV]*)') do
local rankId = string.lower(rank)
if v[rankId] ~= nil then -- has hero that rank?
heroRanks[#heroRanks+1] = rank
local asc = v[rankId]
if asc > 0 then
local imageName, requirement = getImageAndRequirement(hero, ability, epic, asc)
markup[#markup+1] = "[[File:" .. imageName .. "|30px]] '''" .. requirement .. ": '''" .. "<br>\n"
end
markup[#markup+1] = "'''" .. rank .. ":''' " .. ability['description ' .. rankId] .. "<br>\n"
mw.log('::', rank .. ":", ability['description ' .. rankId], (note or ""))
end
end
markup[#markup+1] = '\n'
-- add ranks after headline
markup[rankPlaceholderIdx] = ' (' .. table.concat(heroRanks, '/') .. ')'
else -- trait/ability without ranks (like 'Slow')
local note = ""
local asc = v.ascension
if asc > 0 then
local imageName, requirement = getImageAndRequirement(hero, ability, epic, asc)
note = ", requires [[File:" .. imageName .. "|30px]] " .. requirement
end
markup[#markup+1] = '{| border="0" \n'
markup[#markup+1] = '|-\n'
markup[#markup+1] = "|'''" .. headline(ability, note).. "'''<br>\n"
markup[#markup+1] = ability.description
markup[#markup+1] = '\n'
mw.log(ability.name, note)
mw.log('::', ability.description, "\n")
end
-- add buff/debuff info if present
if ability.effects ~= nil then
markup[#markup+1] = "" -- placeholder to insert heading if there are effects
local idxPlaceholder = #markup -- save index
for _, eid in ipairs(ability.effects) do
fx = effectsData[eid] or { image = "Blank Ability.png", name = eid }
markup[#markup+1] = makeTooltip("effect", eid, '| [[File:' .. fx.image .. '|x25px|link=]] ' .. '[[' .. fx.name .. ']]' )
markup[#markup+1] = ' '
end
if #markup > idxPlaceholder then -- effects present?
markup[idxPlaceholder] = "<br>\n'''Effects '''"
markup[#markup+1] = '\n'
end
end
markup[#markup+1] = '|}\n' -- table end
end
return table.concat(markup)
end
-- Utility function that concats hero.speed/<classicTags>/hero.families/hero.classes
-- only if type='classic' then Armored, Magical, Flying are added
-- hero is loaded from hv/data
-- debug console:
-- =p.herotags({args={'lord zomm'}}) -> output: Slow/Undead/Caster
-- =p.herotags({args={'solaris', tags ='classic'}}) -> output: Magical/Flying/Beast/Healer
function p.herotags(frame)
local args = frame.args
local heroId = string.lower(args[1])
local hero = hvData[heroId]
local tags = {}
if args['tags'] == 'classic' then
mw.log("classic tags requested")
local classicTraits = { armored = true, magical = true, flying = true };
for traitId, _ in pairs(hero.traits) do
local traitId = libTraits.splitRank(traitId)
if classicTraits[traitId] then
tags[#tags+1] = abilitiesData[traitId].name
end
end
end
mw.log('speed', hero.speed)
if (hero.speed == 'Fast' or hero.speed == 'Slow') then
tags[#tags+1] = hero.speed
end
for _, v in pairs(hero.families) do
tags[#tags+1] = v
end
for _, v in pairs(hero.classes) do
tags[#tags+1] = v
end
return table.concat(tags, "/")
end
-- Categorizes Abilities/Traits by given attribute (e.g. damage type). Returns unique categories
-- uses global var abilitiesData (Module:Abilities/data)
-- debug console:
-- =p.categorize({args={'ability', 'Swing/Soul Shred/Spreading Panic/Double Chop', 'damage'}})
-- output: Physical
function p.categorize(frame)
local args = frame.args
local what = args[1] -- reserved (unused) "ability" or "trait"; maybe hero in future
local abilityList = string.lower(args[2]) -- parser list: "soul shred/../.."
local attr = args[3] -- attribute "damage" or "effects", etc
local cats = {}
if what == 'ability' or what == 'trait' then
for abRanked in string.gmatch(abilityList,'([^/]+)') do -- loop over parser list
local abId, rank = libTraits.splitRank(abRanked)
--mw.log('abId: ' .. abId)
local ability = abilitiesData[abId]
if ability ~= nil then
local v = ability[attr] -- or ("None")
if v ~= nil then
if type(v) ~= 'table' then --it's a simple type
cats[v] = true
else -- it's a table like ability.effects, categorize the values
for _, vv in pairs(v) do
cats[vv] = true
end
end
end
end
end
else
return 'Error: invalid keyword (arg1): word "ability" or "trait" expected'
end
local result = {}
for k, _ in pairs(cats) do
result[#result+1] = k
end
table.sort(result)
return table.concat(result, "/")
end
-- ----------------------------------------------------
-- helper for p.resistances
local function fillDebuffResists(resistances, debuffs, resistValue)
for _, debuff in ipairs(debuffs) do
resistances[debuff] = resistValue;
end
end
-- Calculates the resistances for Herobox/Stats
-- debug console:
-- =p.resistances({args={'Solaris'}})
-- output: Immune/33%/0%/0%/0%/18%/18%/../0%/0%/Immune/../6%/Immune
-- (values for resistances (damage types & debuffs))
function p.resistances(frame)
local heroId = frame.args[1]
heroId = string.lower(heroId)
local heroData = hvData[heroId]
if heroData == nil then
return 'ERROR: arg1: unknown heroId ' .. heroId
end
local damageTypes = { "fire", "nature", "water", "light", "dark", "spirit" }
local debuffResists = {
["fire"] = { "burned" },
["nature"] = { "poisoned", "wounded" },
["water"] = { "frozen", "chilled", "cryosleep", "frostbitten" },
-- Hack: "Lightning" is own category but put it to Light so we can use hero.element
["light"] = { "lightning", "shocked", "paralysis" },
["dark"] = { "diseased", "banished" },
["spirit"] = {"feared", "stoned", "possessed", "marked"}
}
local resist2Elem = { ["fire"] = "water"; ["water"] = "nature"; ["nature"] = "fire" }
-- immunities must be a map to check for, used here also for mapping immunity -> debuff
local immunities = {
["fire immune"] = "fire", ["water immune"] = "water", ["nature immune"] = "nature", ["light immune"] = "light", ["dark immune"] = "dark", ["spirit immune"] = "spirit", ["burn immune"] = "burned", ["poison immune"] = "poisoned", ["wound immune"] = "wounded", ["freeze immune"] = "frozen", ["chill immune"] = chilled, ["cryosleep immune"] = "cryosleep", ["frostbite immune"] = "frostbitten", ["lightning immune"] = "lightning", ["shock immune"] = "shocked", ["paralysis immune"] = "paralysis", ["disease immune"] = "diseased", ["banish immune"] = "banished", ["spirit immune"] = "spirit", ["fear immune"] = "feared", ["stone immune"] = "stoned", ["possess immune"] = "possessed", ["mark immune"] = "marked"
}
local resistances = { } -- result
local element = string.lower(heroData.element)
-- fill base resistances (primary, secondary & Spirit)
-- and base debuff resistances depending on element resist.
resistances[element] = 33;
local debuffs = debuffResists[element]
fillDebuffResists(resistances, debuffs, 33)
local resist2 = resist2Elem[element]
local debuffs = debuffResists[resist2]
if resist2 ~= nil then
resistances[resist2] = 18;
fillDebuffResists(resistances, debuffs, 18)
end
resistances['spirit'] = 6;
local debuffs = debuffResists['spirit']
for _, debuff in ipairs(debuffs) do
fillDebuffResists(resistances, debuffs, 6)
end
-- add immunities & resistances (like NubNub's Spirit resist)
local abilitiesData = mw.loadData('Module:Abilities/data')
local traits = heroData['traits']
for traitRanked, _ in pairs(traits) do
local traitId, rank = libTraits.splitRank(traitRanked)
local trait = abilitiesData[traitId]
local effects = trait.effects
if effects ~= nil then
for _, fx in ipairs(effects) do
mw.log("Is immunity?", fx)
if immunities[fx] ~= nil then
resistances[immunities[fx]] = 'Immune'
mw.log('--> yes')
end
end
end
local values = trait.resistances
if values ~= nil then -- it's a map, add percentage, cap at 75%
for resist, percentage in pairs(values) do
if resistances[resist] ~= 'Immune' then
resistances[resist] = math.min((resistances[resist] or 0) + percentage, 75)
end
end
end
end
-- if construct make immune to all debuffs
local isConstruct = false
for _, family in ipairs(heroData.families) do
mw.log(family)
if family == "Construct" then
isConstruct = true
end
end
-- return result values in correct order
local result = {}
for _, damType in ipairs(damageTypes) do
local v = resistances[damType] or 0
if v ~= 'Immune' then v = v .. '%' end
result[#result+1] = v
debuffs = debuffResists[damType]
for _, debuff in ipairs(debuffs) do
local v = resistances[debuff] or 0
if isConstruct then v = 'Immune' end -- override for constructs
if v ~= 'Immune' then v = v .. '%' end
result[#result+1] = v
end
end
return table.concat(result, "/")
end
function createStatsMap()
return { ["Attack"]=0, ["Defense"]=0, ["Skill"]=0, ["Max Health"]=0, ["Crit Chance"]=0,
["Crit Multiplier"]=0, ["Dodge Chance"]=0, ["Damage Penetration"]=0, ["Damage Reduction"]=0,
["Chaos Damage Reduction"]=0 }
end
function epicStatBoosts(heroId, statsOrder)
local epicsData = mw.loadData('Module:Epics/data')
local epic = epicsData[heroId]
if epic == nil then
return { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } -- no epic
end
local boosts = createStatsMap()
local stat1, stat2, stat3 = epic['stats boosted'][1], epic['stats boosted'][2], epic['stats boosted'][3]
boosts[stat1] = 30
boosts[stat2] = (stat2 == 'Crit Chance') and 10 or 20 -- +10% for CRIT else +20%
boosts[stat3] = (stat3 == 'Crit Chance') and 10 or 20
local result = {}
for _, stat in pairs(statsOrder) do
mw.log("esb result:", _, stat, boosts[stat])
result[#result+1] = boosts[stat]
end
return result
end
function passiveStatBoosts(heroId, statsOrder, skillFactor)
local abilitiesData = mw.loadData('Module:Abilities/data')
local boosts = createStatsMap()
local hero = hvData[heroId]
if hero == nil then
return { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, {} -- hero not found
end
local traitDone = {} -- map to filter Traits with ranks like Alrakis' Tempered Soul I-III
local traitIDs = {} -- map to filter doubles like Slow (boosts ATK+HP)
for traitAndRank, _ in pairs(hero.traits) do
mw.log(traitAndRank)
local traitId, rank = libTraits.splitRank(traitAndRank)
local trait = abilitiesData[traitId]
if (traitDone[trait] == nil) then
traitDone[trait] = true
local statBoost = trait["stat boost"]
if statBoost ~= nil and statBoost.target.self then
for statId, boostValue in pairs(statBoost.boosts) do
mw.log("-->", statId, boostValue, skillFactor)
local statBoost = math.floor(boostValue * skillFactor)
if (boostValue < 0) then -- ATK penality for fast!
statBoost = math.floor(boostValue)
end
boosts[statId] = (boosts[statId] or 0) + statBoost
traitIDs[traitId] = true
end
end
end -- traitDone
end
-- convert maps to lists
local result = {}
local resultTraits = {}
for _, stat in pairs(statsOrder) do
result[#result+1] = boosts[stat]
end
for traitId, _ in pairs(traitIDs) do
resultTraits[#resultTraits+1] = traitId
end
return result, resultTraits
end
-- returns the stat boost values for traits (like Slow) and Epic for given Hero.
-- Used by Template:Herobox
-- =p.statBoosts({args={'Lord Zomm'}})
-- Output: 50/0/0/0/0+lord of the undead/slow+20/30/0/0/10
function p.statBoosts(frame)
local heroId = frame.args[1]
heroId = string.lower(heroId)
local baseValues = { -1, -1, -1, -1, 5, 200, 0, 0, 0, 0 }
-- -1 = flag to indicate it's ATK, DEF, .. with is affected by Stars and Epic
local statsOrder = { "Attack", "Defense", "Skill", "Max Health", "Crit Chance",
"Crit Multiplier", "Dodge Chance", "Damage Penetration", "Damage Reduction",
"Chaos Damage Reduction" } -- order of returned values
local hero = hvData[heroId]
local rarity = { ['Bronze'] = 0, ['Silver'] = 5, ['Gold'] = 15, ['Legendary'] = 30 }
local heroRarity = hero.rarity
local rarityFactor = 1 + (rarity[heroRarity] / 100)
local skillBonus = hero["max skill"] * rarityFactor * 2
local skillFactor = 1 + (skillBonus / 20000)
local epicBoosts = epicStatBoosts(heroId, statsOrder)
local passiveBoosts, traits = passiveStatBoosts(heroId, statsOrder, skillFactor)
local baseStats = { hero["max attack"], hero["max defense"], hero["max skill"], hero["max health"], 5, 200, 0, 0, 0, 0 }
local starBoost = { 100 * rarityFactor, 100 * rarityFactor, 100 * rarityFactor, 100 * rarityFactor, 0, 0, 0, 0, 0, 0 }
local totalBoosts = {}
for ii, epcBoost in ipairs(epicBoosts) do
if (ii <= 4) then
totalBoosts[#totalBoosts+1] = math.floor((100 + passiveBoosts[ii] + epcBoost) * rarityFactor * 2 + .5) - 100 -- the *2 is the 100% bonus of 6 stars
mw.log(baseStats[ii], passiveBoosts[ii], epcBoost, rarityFactor, 2, "=", totalBoosts[#totalBoosts])
else
totalBoosts[#totalBoosts+1] = baseStats[ii] + passiveBoosts[ii] + epcBoost
end
end
-- fix CRIT Chance. It's +1% for ~81.6 SKL, max 50
local critSkillBonus = math.floor(skillBonus / 81.6 + 0.5)
mw.log("skillBonus", critSkillBonus)
totalBoosts[5] = math.min(totalBoosts[5] + critSkillBonus, 50)
starBoost[5] = critSkillBonus
-- Base + Star Bonus + Trait Bonus + Epic Bonus + Total + Traits
return table.concat(baseStats, "/") .. "+"
.. table.concat(starBoost, "/") .. "+"
.. table.concat(passiveBoosts, "/") .. "+"
.. table.concat(epicBoosts, "/") .. "+"
.. table.concat(totalBoosts, "/") .. "+"
.. table.concat(traits, "/")
end
-- =p.ascensionCosts({args={'Lord Zomm'}})
function p.ascensionCosts(frame)
local heroId = frame.args[1]
heroId = string.lower(heroId)
local hero = hvData[heroId]
if hero == nil then
return -1 -- hero not found
end
local evosData = mw.loadData('Module:Evos/data')
local results = { 0, 0}
for idx, heroEvos in ipairs(hero.evos) do
local score = 0
for evo, amount in pairs(heroEvos) do
evo = string.lower(evo)
score = score + evosData[evo]["gem cost"] * amount
--mw.log(idx, evo, amount, evosData[evo]["gem cost"], score)
end
results[idx] = score
--mw.log("=>", score)
end
return table.concat(results, "/")
end
-- =p.combatStyle({args={'Lord Zomm'}})
-- calcualte the combat style of the given hero (1=basic attacks <==> 9=special attacks)
function p.combatStyle(frame)
local heroId = frame.args[1]
heroId = string.lower(heroId)
local hero = hvData[heroId]
if hero == nil then
return -1 -- hero not found
end
local skipRanks = {}
local style = 1 -- let's start with basic attacks and adjust it when special attacks deal damage
local abilitiesData = mw.loadData('Module:Abilities/data')
local abilities = hero['abilities']
local epicsData = mw.loadData('Module:Epics/data')
for ab, _ in pairs(abilities) do
local ability = abilitiesData[ab]
if ability == nil then -- not found. Check if it has a rank (like Furnace')
ab = libTraits.splitRank(ab)
ability = abilitiesData[ab]
if skipRanks[ab] ~= nil then
ability = nil -- skip this. already processed. TODO: but might be higher rank
else
skipRanks[ab] = true
end
end
if ability ~= nil then
if ability.damage ~= nil then
mw.log(ab, ability.damage, ability.targets) -- log
style = style + 2
if (ability.targets or 0) > 1 then -- bonus for multi targets
style = style + 1
end
end
end
end
-- Check dual wielder like Grog-Gnog, Hagrim
if (hero['basic attack'].targets or 1) > 1 then
style = style - 1
end
-- if hero has an epic adjust style a bit to "basic attack"
if epicsData[heroId] ~= nil then
style = style - 1
mw.log("epic") -- log
if (epicsData[heroId].targets or 0) >= 4 then -- AoE attack?
style = style - 1
end
end
-- keep it in range 1 to 9
style = math.max(math.min(style, 9), 1)
return style
end
return p -- </nowiki>