Documentation for this module may be created at Module:Sandbox/doc
--
-- This module contains some helper functions for the design Template:Herobox
--
local p = {}
function calcEpicStatBoost(idx, stat)
local boost = 20
if idx == 1 then
boost = 30
end
if stat == 'CRIT' or stat == 'Crit' then
boost = boost / 2
end
return boost
end
-- debug console: =p.statsTable({args={'lord zomm'}})
function p.statsTable(frame)
local hvData = mw.loadData('Module:Hv/data')
local abilitiesData = mw.loadData('Module:Abilities/data')
local epicsData = mw.loadData('Module:Epics/data')
local heroId = frame.args[1]
local statsText = { "Attack", "Defense", "Health", "Skill", "Crit Chance", "Crit Multiplier", "Dodge Chance" };
local stats = { "ATK", "DEF", "HP", "SKL", "CRIT", "CRX", "DDGE" };
local statsIdx = { ["ATK"]=1, ["DEF"]=2, ["HP"]=3, ["SKL"]=4, ["CRIT"]=5, ["CRX"]=6, ["DDGE"]=7 };
local epicIdx = { ["Atk"]=1, ["Def"]=2, ["Health"]=3, ["Skill"]=4 };
local boostPassive = { 0, 0, 0, 0, 0, 0, 0 };
local boostEpic = { 0, 0, 0, 0, 0, 0, 0 };
local boostTotal = { 0, 0, 0, 0, 0, 0, 0 };
heroId = string.lower(heroId)
local hero = hvData[heroId]
for traitAndRank, _ in pairs(hero.traits) do
mw.log(traitAndRank)
local traitId, rank = string.match(traitAndRank, "([a-z '\(\)]+)-?([iv]*)")
local trait = abilitiesData[traitId]
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)
local idx = statsIdx[statId]
if idx ~= nil then
boostPassive[idx] = boostPassive[idx] + boostValue
end
end
end
end
epic = epicsData[heroId]
if epic ~= nil then
for idx, statId in pairs(epic["stats boosted"]) do
local boostValue = calcEpicStatBoost(idx, statId)
mw.log(idx, statId, boostValue)
local idx = epicIdx[statId]
if idx ~= nil then
boostEpic[idx] = boostEpic[idx] + boostValue
end
end
end
mw.log('result --------------------')
for ii, statId in ipairs(stats) do
local idx = statsIdx[statId]
mw.log(ii, statId, boostPassive[idx], boostEpic[idx], boostTotal[idx])
end
end
-- 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 = {}
local traitsOrAbilities = heroData[attrTraitsOrAbilities]
for abRanked, _ in pairs(traitsOrAbilities) do
local ab, rank = string.match(abRanked, "([a-z '\(\)]+)-?([iv]*)")
-- mw.log(abRanked, '=>', ab, ' + ', rank)
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
end
local result = {}
for ab, rank in pairs(highestRanks) do
--mw.log (ab, '-' ,rank)
if rank > 0 then
result[#result+1] = ab .. '-' .. (rankToSign[rank])
else
result[#result+1] = ab
end
end
-- sort by ascension
table.sort(result, 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 = traitsOrAbilities[a]
local siB = traitsOrAbilities[b]
if siA == siB then
return a < b
else
return siA < siB
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'] = { -- = 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)
local abtr, rank = string.match(what, '([a-zA-Z \'\(\)]+)-?([iv]*)')
local hasRanks = false
local ability = abilitiesData[abtr]
if ability == nil then
error('ERROR: unknown trait or ability: ' .. (abtr or "nil"))
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
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
function getImageFileName(hero, epic, asc)
local filename
if 0 <= asc and asc <= 2 then
filename = hero['token ' .. (asc+1)]
elseif asc == 3 then
filename = epic['name'] .. '.png'
elseif asc == 4 then
filename = hero['token 3'] -- TODO: get image
end
if filename == nil or filename == "" then
filename = "Blank token.png"
end
return filename
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 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
local epicTrait = hero['epic trait']
if epicTrait ~= nil then
prepareAbilityData(epicTrait, 3, result, sortindex) -- make epic a dummy '3A'
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 textReqCondition = { '1st Ascension', '2nd Ascension', 'Epic', 'Skin' }
local markup = {}
local rankPlaceholderIdx = 0
for _, k in pairs(sortindex) do
v = result[k]
markup[#markup+1] = '===' .. abilitiesData[k].name
markup[#markup+1] = '' -- placeholder for ranks (I/II..)
rankPlaceholderIdx = #markup
markup[#markup+1] = '===\n'
-- <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 filename = getImageFileName(hero, epic, asc)
markup[#markup+1] = "[[File:" .. filename .. "|30px]] '''" .. textReqCondition[asc] .. ": '''" .. "<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'
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 filename = getImageFileName(hero, epic, asc)
note = ", requires [[File:" .. filename .. "|30px]] " .. textReqCondition[asc]
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'
markup[#markup+1] = '|}\n'
mw.log(ability.name, note)
mw.log('::', ability.description, "\n")
end
end
return table.concat(markup)
end
-- Utility function that concats hv.categories/hv.families/hv.classes
-- debug console: =p.herotags({args={'lord zomm'}})
-- output > Legendady/Undead/Caster
function p.herotags(frame)
local args = frame.args
local heroId = string.lower(args[1])
local hero = hvData[heroId]
local tags = {}
for _, v in pairs(hero.categories) do
tags[#tags+1] = v
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
--
function categorize_addToMap(map, key, value, saveValue)
if saveValue then
if map[key] == nil then map[key] = {} end
local arr = map[key]
arr[#arr+1] = value
else
map[key] = true
end
end
-- New test: get tokens (like a filter/view) from Dungeons/data).
-- The data is not static. It need to be calculated each time again.
-- Test to see the impact to wiki limits like lua CPU time, memory, etc.
local dungeonData = mw.loadData('Module:Dungeons/data')
function dungeonOrderCmp(d1, d2)
--mw.log("dungeonOrderCmp", d1, d2, dungeonData[d1].idx, dungeonData[d2].idx)
return dungeonData[d1].idx < dungeonData[d2].idx
end
function mapTokenLocations()
local modeLocations
local locations
local tokensByHero = {}
for dungeonId, dungeon in pairs(dungeonData) do
--local mode, chapter = string.match(dungeonId,'(..):([0-9]+-[0-9]+)');
--mw.log(mode, chapter)
if dungeon.token ~= nil then
locations = tokensByHero[dungeon.token] or {}
locations[#locations+1] = dungeonId
tokensByHero[dungeon.token] = locations
end
end
-- sort locations (normal, chall, boss, then chapter)
for heroId, locations in pairs(tokensByHero) do
table.sort(locations, dungeonOrderCmp)
end
return tokensByHero;
end
-- =p.tokenLocations({args={'Zurk', '++'}})
-- output: "2-1/4-1/ ..." (dungeon chapters)
function p.tokenLocationsNoLoadData(frame)
local args = frame.args
local heroId = string.lower(args[1] or "") -- the hero
local listSeparator = args[2] or "+" -- sep for normal mode/challenge mode/boss mode
local tokensByHero = mapTokenLocations()
local result = tokensByHero[heroId] or {}
for k,v in pairs(result) do
mw.log(k,v)
end
return table.concat(result, "/")
end
-- =p.tokenLocations({args={'Zurk', '++'}})
-- output: "2-1/4-1/ ..." (dungeon chapters)
function p.tokenLocationsLoadData(frame)
local args = frame.args
local heroId = string.lower(args[1] or "") -- the hero
local listSeparator = args[2] or "+" -- sep for normal mode/challenge mode/boss mode
local dungeonData = mw.loadData('Module:Sandbox/data')
local resultData = dungeonData[heroId] or {}
mw.log("tokenLocations", resultData)
local result = {}
for k,v in pairs(resultData) do
mw.log(k,v)
result[#result+1] = v
end
return table.concat(result, "/")
end
return p