Dungeon Boss Wiki
Advertisement

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
Advertisement