Module:Attacks/Tables: Difference between revisions

From Melvor Idle
(Don't return table when number of attacks is zero)
(Implement support for spells, familiars & refactor into single combined function for monster & player attacks table)
Line 1: Line 1:
local p = {}
local p = {}


local Constants require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Monsters = require('Module:Monsters')
local Monsters = require('Module:Monsters')
local Magic = require('Module:Magic')
local Attacks = require('Module:Attacks')
local Attacks = require('Module:Attacks')


function p._getMonsterSpecialAttackTable(effectDefn)
function p._getSpecialAttackTable(effectDefn, categories, sourceHeaderLabel, includeSource)
     local spAttTable = {}
     local spAttTable = {}
     local attacks = Attacks.getAttacks(function(attack)
     local attacks = Attacks.getAttacks(function(attack)
Line 17: Line 18:
                                           end
                                           end
                                       end)
                                       end)
    local includeCat = {}
    for i, category in ipairs(categories) do
        includeCat[category] = true
    end


     -- Compile table of monsters & chances for each attack
     -- Compile a list of monsters, items, spells, etc. for each included attack
     for i, spAtt in ipairs(attacks) do
     for i, spAtt in ipairs(attacks) do
         if Shared.tableCount(spAtt.monsters) > 0 then
        -- Monsters
            spAttTable[i] = {}
         if includeCat['Monster'] then
 
             for j, monsterID in ipairs(spAtt.monsters) do
             for j, monsterID in ipairs(spAtt.monsters) do
                 local monster = Monsters.getMonsterByID(monsterID)
                 local monster = Monsters.getMonsterByID(monsterID)
Line 41: Line 45:
                 end
                 end


                if spAttTable[i][attChance] == nil then
                 table.insert(spAttTable, { idx = i, source = 'Monster', sourceSort = monster.name, sourceText = Icons.Icon({ monster.name, type = 'monster' }), chance = attChance, descType = 'monster' })
                    spAttTable[i][attChance] = {}
                end
                 table.insert(spAttTable[i][attChance], Icons.Icon({ monster.name, type = 'monster' }))
             end
             end
         end
         end
    end


    -- Generate output table
        -- Items/Weapons
    if Shared.tableCount(spAttTable) == 0 then
        if includeCat['Item'] then
    return ''
            for j, itemID in ipairs(spAtt.items) do
    end
                local item = Items.getItemByID(itemID)
   
                table.insert(spAttTable, { idx = i, source = 'Weapon', sourceSort = item.name, sourceText = Icons.Icon({ item.name, type = 'item' }), chance = spAtt.defaultChance, descType = 'player' })
    local resultPart = {}
            end
    table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
        end
    table.insert(resultPart, '\r\n|- class="headerRow-0"')
    table.insert(resultPart, '\r\n!Name!!style="min-width:225px"|Monsters!!Chance!!Effect')


    for i, spAttData in Shared.skpairs(spAttTable) do
        -- Spells
         local spAtt = attacks[i]
         if includeCat['Spell'] then
        local firstRow = true
             for j, spellID in ipairs(spAtt.spells) do
        local rowsSpanned = Shared.tableCount(spAttData)
                local spell = Magic.getSpellByID(spellID[1], spellID[2])
        local rowSuffix = ''
                 table.insert(spAttTable, { idx = i, source = 'Spell', sourceSort = spell.name, sourceText = Icons.Icon({ spell.name, type = 'spell' }), chance = spAtt.defaultChance, descType = 'player' })
        if rowsSpanned > 1 then
             rowSuffix = '|rowspan="' .. rowsSpanned .. '"'
        end
        for chance, iconList in Shared.skpairs(spAttData) do
            table.insert(resultPart, '\r\n|-')
            if firstRow then
                 table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.name)
            end
            table.insert(resultPart, '\r\n|data-sort-value="' .. spAtt.name .. '"| ' .. table.concat(iconList, '<br/>'))
            table.insert(resultPart, '\r\n|data-sort-value="' .. chance .. '"| ' .. Shared.round(chance, 2, 0) .. '%')
            if firstRow then
                table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.description.monster)
                firstRow = false
             end
             end
         end
         end
     end
     end
    table.insert(resultPart, '\r\n|}')


     return table.concat(resultPart)
     -- Summoning familiars. Any effects inflicted by combat familiars aren't actually special
end
    -- attacks, therefore the handling here is a bit different and outside of the above attack loop
 
    if includeCat['Familiar'] then
function p._getItemSpecialAttackTable(effectDefn)
        local famIdx = Shared.tableCount(attacks) + 1
  local spAttTable = {}
        local familiars = Items.getItems(function(item)
  local attacks = Attacks.getAttacks(function(attack)
                                            if item.type == 'Familiar' and Items._getItemStat(item, 'summoningMaxhit') ~= nil and item.modifiers ~= nil then
                                        if effectDefn == nil then
                                                local famAttack = { prehitEffects = {}, onhitEffects = { { type = 'Modifier', subtype = 'Familiar', modifiers = item.modifiers } } }
                                            return true
                                                if effectDefn == nil then
                                        else
                                                    return Shared.tableCount(Attacks.getAttackEffects(famAttack)) > 0
                                            return Attacks.attackHasEffect(attack, effectDefn)
                                                else
                                        end
                                                    return Attacks.attackHasEffect(famAttack, effectDefn)
                                    end)
                                                end
 
                                            end
    -- Compile table of items for each attack
                                            return false
    for i, spAtt in ipairs(attacks) do
                                        end)
        if Shared.tableCount(spAtt.items) > 0 then
        for j, familiar in ipairs(familiars) do
             spAttTable[i] = { Icons = {} }
            -- For chance, assume the first modifier we come across has the chance, which is pretty lazy
 
             local famChance, famDesc = 0, ''
             for j, itemID in ipairs(spAtt.items) do
             for modName, modVal in pairs(familiar.modifiers) do
                 local item = Items.getItemByID(itemID)
                 if type(modVal) == 'table' and type(modVal[1]) == 'number' then
                if spAttTable[i]['sortName'] == nil then
                     famChance = modVal[1]
                     spAttTable[i]['sortName'] = item.name
                elseif type(modVal) == 'number' then
                    famChance = modVal
                else
                    famChance = 0
                 end
                 end
                 table.insert(spAttTable[i]['Icons'], Icons.Icon({ item.name, type = 'item' }))
                 famDesc = Constants._getModifierText(modName, modVal, false)
                break
             end
             end
            table.insert(spAttTable, { idx = famIdx, source = 'Familiar', sourceSort = familiar.name, sourceText = Icons.Icon({ familiar.name, type = 'item' }), chance = famChance or 0, descType = 'player' })
            -- Slap a dummy entry into the attacks table for this familiar
            attacks[famIdx] = { name = familiar.name .. ' (Familiar)', description = { player = famDesc } }
            famIdx = famIdx + 1
         end
         end
     end
     end


     -- Generate output table
     -- Nothing to output if there are no row definitions
     if Shared.tableCount(spAttTable) == 0 then
     if Shared.tableCount(spAttTable) == 0 then
     return ''
     return ''
     end
     end


    -- Sort entries into desired order and generate stats to determine row spans:
    -- By attack index, description type (monster/player), chance, source, then source name (weapon/item/etc.)
    table.sort(spAttTable, function (a, b)
                              local sortKeys = { 'idx', 'descType', 'chance', 'source', 'sourceSort' }
                              for i, key in ipairs(sortKeys) do
                                  if a[key] ~= b[key] then
                                      return a[key] < b[key]
                                  end
                              end
                              return false
                          end)
    -- Determine row counts for grouping/rowspans
    local rowCounts = {}
    for i, rowDefn in ipairs(spAttTable) do
        local idx, dt, chance = rowDefn.idx, rowDefn.descType, rowDefn.chance
        if rowCounts[idx] == nil then
            rowCounts[idx] = { rows = 0 }
        end
        if rowCounts[idx][dt] == nil then
            rowCounts[idx][dt] = { rows = 0 }
        end
        if rowCounts[idx][dt][chance] == nil then
            rowCounts[idx][dt][chance] = 0
        end
        rowCounts[idx]['rows'] = rowCounts[idx]['rows'] + 1
        rowCounts[idx][dt]['rows'] = rowCounts[idx][dt]['rows'] + 1
        rowCounts[idx][dt][chance] = rowCounts[idx][dt][chance] + 1
    end
    -- Generate output table
     local resultPart = {}
     local resultPart = {}
     table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
     table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
     table.insert(resultPart, '\r\n|- class="headerRow-0"')
     table.insert(resultPart, '\r\n|- class="headerRow-0"')
     table.insert(resultPart, '\r\n!style="min-width:100px"|Name!!style="min-width:180px"|Weapon(s)!!Chance!!Effect')
     table.insert(resultPart, '\r\n!Name!!style="min-width:225px"| ' .. sourceHeaderLabel .. (includeSource and '!!Type' or '') .. '!!Chance!!Effect')


     for i, spAttData in Shared.skpairs(spAttTable) do
    local firstRow = { idx = true, descType = true, chance = true }
         local spAtt = attacks[i]
    local prevRowVal = { idx = 0, descType = '', chance = 0 }
         table.sort(spAttData.Icons, function(a, b) return a < b end)
    local resetOnChange = {
        idx = { 'idx', 'descType', 'chance' },
        descType = { 'descType', 'chance' },
        chance = { 'chance' }
    }
    local rowSuffix = ''
     for i, spAttRow in ipairs(spAttTable) do
        local spIdx = spAttRow.idx
         local spAtt = attacks[spIdx]
         -- Determine if it's the first row for any of our groupings
        local resetKeys = {}
        for key, val in pairs(prevRowVal) do
            if spAttRow[key] ~= prevRowVal[key] then
                for j, keyName in ipairs(resetOnChange[key]) do
                    resetKeys[keyName] = true
                end
            end
            prevRowVal[key] = spAttRow[key]
        end
        for key, val in pairs(firstRow) do
            firstRow[key] = (resetKeys[key] ~= nil)
        end


         table.insert(resultPart, '\r\n|-')
         table.insert(resultPart, '\r\n|-')
         table.insert(resultPart, '\r\n| ' .. spAtt.name)
         if firstRow.idx then
         table.insert(resultPart, '\r\n|data-sort-value="' .. spAttData.sortName .. '"| ' .. table.concat(spAttData.Icons, '<br/>'))
            rowSuffix = (rowCounts[spIdx]['rows'] > 1 and '|rowspan="' .. rowCounts[spIdx]['rows'] .. '"') or ''
        table.insert(resultPart, '\r\n|data-sort-value="' .. spAtt.defaultChance .. '"| ' .. spAtt.defaultChance .. '%')
            table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.name)
         table.insert(resultPart, '\r\n| ' .. spAtt.description.player)
        end
         table.insert(resultPart, '\r\n|data-sort-value="' .. spAttRow.sourceSort .. '"| ' .. spAttRow.sourceText)
        if includeSource then
            table.insert(resultPart, '\r\n| ' .. spAttRow.source)
        end
        if firstRow.chance then
            rowSuffix = (rowCounts[spIdx][spAttRow.descType][spAttRow.chance] > 1 and 'rowspan="' .. rowCounts[spIdx][spAttRow.descType][spAttRow.chance] .. '" ') or ''
            table.insert(resultPart, '\r\n|' .. rowSuffix .. 'data-sort-value="' .. spAttRow.chance .. '" style="text-align:right;"| ' .. Shared.round(spAttRow.chance, 2, 0) .. '%')
         end
        if firstRow.descType then
            rowSuffix = (rowCounts[spIdx][spAttRow.descType]['rows'] > 1 and '|rowspan="' .. rowCounts[spIdx][spAttRow.descType]['rows'] .. '"') or ''
            table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt['description'][spAttRow.descType])
        end
     end
     end
     table.insert(resultPart, '\r\n|}')
     table.insert(resultPart, '\r\n|}')
Line 137: Line 193:
function p.getSpecialAttackTable(frame)
function p.getSpecialAttackTable(frame)
     local args = frame.args ~= nil and frame.args or frame
     local args = frame.args ~= nil and frame.args or frame
     local tableType = args[1]
     local tableCategories = {'Monster', 'Item', 'Spell', 'Familiar'}
     local effectName = args[2]
    if args[1] ~= nil and args[1] ~= '' then
        tableCategories = Shared.splitString(args[1], ',')
    end
     local effectName = args['effect']
    local sourceHeaderLabel = (args['sourceHeader'] ~= '' and args['sourceHeader']) or 'Source'
    local includeSource = true
    if args['includeSource'] ~= nil and string.lower(args['includeSource']) == 'false' then
        includeSource = false
    end
     local effectDefn = nil
     local effectDefn = nil


Line 154: Line 218:
     end
     end


     if tableType == 'Monster' then
     return p._getSpecialAttackTable(effectDefn, tableCategories, sourceHeaderLabel, includeSource)
        return p._getMonsterSpecialAttackTable(effectDefn)
    elseif tableType == 'Item' then
        return p._getItemSpecialAttackTable(effectDefn)
    else
        return 'ERROR: Invalid table type, must be one of: Item, Monster[[Category:Pages with script errors]]'
    end
end
end


return p
return p

Revision as of 23:54, 9 November 2021

Documentation for this module may be created at Module:Attacks/Tables/doc

local p = {}

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Monsters = require('Module:Monsters')
local Magic = require('Module:Magic')
local Attacks = require('Module:Attacks')

function p._getSpecialAttackTable(effectDefn, categories, sourceHeaderLabel, includeSource)
    local spAttTable = {}
    local attacks = Attacks.getAttacks(function(attack)
                                           if effectDefn == nil then
                                               return true
                                           else
                                               return Attacks.attackHasEffect(attack, effectDefn)
                                           end
                                       end)
    local includeCat = {}
    for i, category in ipairs(categories) do
        includeCat[category] = true
    end

    -- Compile a list of monsters, items, spells, etc. for each included attack
    for i, spAtt in ipairs(attacks) do
        -- Monsters
        if includeCat['Monster'] then
            for j, monsterID in ipairs(spAtt.monsters) do
                local monster = Monsters.getMonsterByID(monsterID)
                local overrideChance = (monster.overrideSpecialChances ~= nil and Shared.tableCount(monster.overrideSpecialChances) > 0)
                local attChance = spAtt.defaultChance
                if overrideChance then
                    local attIdx = nil
                    for k, monsterAttack in ipairs(monster.specialAttacks) do
                        local attID = (type(monsterAttack) == 'table' and monsterAttack.id) or monsterAttack
                        if spAtt.id == attID then
                            attIdx = k
                            break
                        end
                    end
                    if attIdx ~= nil then
                        attChance = monster.overrideSpecialChances[attIdx]
                    end
                end

                table.insert(spAttTable, { idx = i, source = 'Monster', sourceSort = monster.name, sourceText = Icons.Icon({ monster.name, type = 'monster' }), chance = attChance, descType = 'monster' })
            end
        end

        -- Items/Weapons
        if includeCat['Item'] then
            for j, itemID in ipairs(spAtt.items) do
                local item = Items.getItemByID(itemID)
                table.insert(spAttTable, { idx = i, source = 'Weapon', sourceSort = item.name, sourceText = Icons.Icon({ item.name, type = 'item' }), chance = spAtt.defaultChance, descType = 'player' })
            end
        end

        -- Spells
        if includeCat['Spell'] then
            for j, spellID in ipairs(spAtt.spells) do
                local spell = Magic.getSpellByID(spellID[1], spellID[2])
                table.insert(spAttTable, { idx = i, source = 'Spell', sourceSort = spell.name, sourceText = Icons.Icon({ spell.name, type = 'spell' }), chance = spAtt.defaultChance, descType = 'player' })
            end
        end
    end

    -- Summoning familiars. Any effects inflicted by combat familiars aren't actually special
    -- attacks, therefore the handling here is a bit different and outside of the above attack loop
    if includeCat['Familiar'] then
        local famIdx = Shared.tableCount(attacks) + 1
        local familiars = Items.getItems(function(item)
                                             if item.type == 'Familiar' and Items._getItemStat(item, 'summoningMaxhit') ~= nil and item.modifiers ~= nil then
                                                 local famAttack = { prehitEffects = {}, onhitEffects = { { type = 'Modifier', subtype = 'Familiar', modifiers = item.modifiers } } }
                                                 if effectDefn == nil then
                                                     return Shared.tableCount(Attacks.getAttackEffects(famAttack)) > 0
                                                 else
                                                     return Attacks.attackHasEffect(famAttack, effectDefn)
                                                 end
                                             end
                                             return false
                                         end)
        for j, familiar in ipairs(familiars) do
            -- For chance, assume the first modifier we come across has the chance, which is pretty lazy
            local famChance, famDesc = 0, ''
            for modName, modVal in pairs(familiar.modifiers) do
                if type(modVal) == 'table' and type(modVal[1]) == 'number' then
                    famChance = modVal[1]
                elseif type(modVal) == 'number' then
                    famChance = modVal
                else
                    famChance = 0
                end
                famDesc = Constants._getModifierText(modName, modVal, false)
                break
            end

            table.insert(spAttTable, { idx = famIdx, source = 'Familiar', sourceSort = familiar.name, sourceText = Icons.Icon({ familiar.name, type = 'item' }), chance = famChance or 0, descType = 'player' })
            -- Slap a dummy entry into the attacks table for this familiar
            attacks[famIdx] = { name = familiar.name .. ' (Familiar)', description = { player = famDesc } }
            famIdx = famIdx + 1
        end
    end

    -- Nothing to output if there are no row definitions
    if Shared.tableCount(spAttTable) == 0 then
    	return ''
    end

    -- Sort entries into desired order and generate stats to determine row spans:
    -- By attack index, description type (monster/player), chance, source, then source name (weapon/item/etc.)
    table.sort(spAttTable, function (a, b)
                               local sortKeys = { 'idx', 'descType', 'chance', 'source', 'sourceSort' }
                               for i, key in ipairs(sortKeys) do
                                   if a[key] ~= b[key] then
                                       return a[key] < b[key]
                                   end
                               end
                               return false
                           end)
    -- Determine row counts for grouping/rowspans
    local rowCounts = {}
    for i, rowDefn in ipairs(spAttTable) do
        local idx, dt, chance = rowDefn.idx, rowDefn.descType, rowDefn.chance
        if rowCounts[idx] == nil then
            rowCounts[idx] = { rows = 0 }
        end
        if rowCounts[idx][dt] == nil then
            rowCounts[idx][dt] = { rows = 0 }
        end
        if rowCounts[idx][dt][chance] == nil then
            rowCounts[idx][dt][chance] = 0
        end
        rowCounts[idx]['rows'] = rowCounts[idx]['rows'] + 1
        rowCounts[idx][dt]['rows'] = rowCounts[idx][dt]['rows'] + 1
        rowCounts[idx][dt][chance] = rowCounts[idx][dt][chance] + 1
    end

    -- Generate output table
    local resultPart = {}
    table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
    table.insert(resultPart, '\r\n|- class="headerRow-0"')
    table.insert(resultPart, '\r\n!Name!!style="min-width:225px"| ' .. sourceHeaderLabel .. (includeSource and '!!Type' or '') .. '!!Chance!!Effect')

    local firstRow = { idx = true, descType = true, chance = true }
    local prevRowVal = { idx = 0, descType = '', chance = 0 }
    local resetOnChange = {
        idx = { 'idx', 'descType', 'chance' },
        descType = { 'descType', 'chance' },
        chance = { 'chance' }
    }
    local rowSuffix = ''
    for i, spAttRow in ipairs(spAttTable) do
        local spIdx = spAttRow.idx
        local spAtt = attacks[spIdx]
        -- Determine if it's the first row for any of our groupings
        local resetKeys = {}
        for key, val in pairs(prevRowVal) do
            if spAttRow[key] ~= prevRowVal[key] then
                for j, keyName in ipairs(resetOnChange[key]) do
                    resetKeys[keyName] = true
                end
            end
            prevRowVal[key] = spAttRow[key]
        end
        for key, val in pairs(firstRow) do
            firstRow[key] = (resetKeys[key] ~= nil)
        end

        table.insert(resultPart, '\r\n|-')
        if firstRow.idx then
            rowSuffix = (rowCounts[spIdx]['rows'] > 1 and '|rowspan="' .. rowCounts[spIdx]['rows'] .. '"') or ''
            table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.name)
        end
        table.insert(resultPart, '\r\n|data-sort-value="' .. spAttRow.sourceSort .. '"| ' .. spAttRow.sourceText)
        if includeSource then
            table.insert(resultPart, '\r\n| ' .. spAttRow.source)
        end
        if firstRow.chance then
            rowSuffix = (rowCounts[spIdx][spAttRow.descType][spAttRow.chance] > 1 and 'rowspan="' .. rowCounts[spIdx][spAttRow.descType][spAttRow.chance] .. '" ') or ''
            table.insert(resultPart, '\r\n|' .. rowSuffix .. 'data-sort-value="' .. spAttRow.chance .. '" style="text-align:right;"| ' .. Shared.round(spAttRow.chance, 2, 0) .. '%')
        end
        if firstRow.descType then
            rowSuffix = (rowCounts[spIdx][spAttRow.descType]['rows'] > 1 and '|rowspan="' .. rowCounts[spIdx][spAttRow.descType]['rows'] .. '"') or ''
            table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt['description'][spAttRow.descType])
        end
    end
    table.insert(resultPart, '\r\n|}')

    return table.concat(resultPart)
end

function p.getSpecialAttackTable(frame)
    local args = frame.args ~= nil and frame.args or frame
    local tableCategories = {'Monster', 'Item', 'Spell', 'Familiar'}
    if args[1] ~= nil and args[1] ~= '' then
        tableCategories = Shared.splitString(args[1], ',')
    end
    local effectName = args['effect']
    local sourceHeaderLabel = (args['sourceHeader'] ~= '' and args['sourceHeader']) or 'Source'
    local includeSource = true
    if args['includeSource'] ~= nil and string.lower(args['includeSource']) == 'false' then
        includeSource = false
    end
    local effectDefn = nil

    if effectName ~= nil and effectName ~= '' then
        effectDefn = Attacks.effectDefinition[effectName]
        if effectDefn == nil then
            local validEffectNames = {}
            for k, v in pairs(Attacks.effectDefinition) do
                table.insert(validEffectNames, k)
            end
            table.sort(validEffectNames, function(a, b) return a < b end)

            return 'ERROR: Invalid effect name "' .. effectName .. '", must be one of: ' .. table.concat(validEffectNames, ', ') .. '[[Category:Pages with script errors]]'
        end
    end

    return p._getSpecialAttackTable(effectDefn, tableCategories, sourceHeaderLabel, includeSource)
end

return p