Difference between revisions of "Module:Attacks/Tables"
From Melvor Idle
Falterfire (talk | contribs) m (typing is hard.) |
(Fix for unknown items) |
||
(One intermediate revision by the same user not shown) | |||
Line 10: | Line 10: | ||
function p._getSpecialAttackTable(effectDefn, categories, sourceHeaderLabel, includeSource) | 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) | |
− | + | if item ~= nil then | |
− | + | table.insert(spAttTable, { idx = i, source = 'Weapon', sourceSort = item.name, sourceText = Icons.Icon({ item.name, type = 'item' }), chance = spAtt.defaultChance, descType = 'player' }) | |
− | + | end | |
+ | 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 '' | |
− | + | local spAttDesc = spAtt['description'][spAttRow.descType] | |
− | + | --Adding the time between hits and total duration as a note at the end of the special attack description | |
− | + | local spAttInterval = spAtt.attackInterval ~= nil and spAtt.attackInterval or -1 | |
− | + | if(spAttInterval ~= -1 and spAtt.damage ~= nil and Shared.tableCount(spAtt.damage) > 0) then | |
− | + | spAttDesc = spAttDesc..'<br/>(' | |
− | + | local spAttDuration = spAttInterval * (spAtt.attackCount - 1) | |
− | + | spAttDesc = spAttDesc..Shared.round(spAttInterval / 1000, 2, 2)..'s delay between attacks.' | |
− | + | if spAtt.attackCount ~= nil and spAtt.attackCount > 2 then | |
− | + | spAttDesc = spAttDesc..' '..Shared.round(spAttDuration / 1000, 2, 2)..'s total duration' | |
− | + | end | |
− | + | spAttDesc = spAttDesc..')' | |
− | + | end | |
− | + | table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAttDesc) | |
− | + | end | |
− | + | end | |
− | + | table.insert(resultPart, '\r\n|}') | |
− | + | return table.concat(resultPart) | |
end | end | ||
function p.getSpecialAttackTable(frame) | 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 | end | ||
Latest revision as of 22:06, 31 January 2022
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) if item ~= nil then table.insert(spAttTable, { idx = i, source = 'Weapon', sourceSort = item.name, sourceText = Icons.Icon({ item.name, type = 'item' }), chance = spAtt.defaultChance, descType = 'player' }) end 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 '' local spAttDesc = spAtt['description'][spAttRow.descType] --Adding the time between hits and total duration as a note at the end of the special attack description local spAttInterval = spAtt.attackInterval ~= nil and spAtt.attackInterval or -1 if(spAttInterval ~= -1 and spAtt.damage ~= nil and Shared.tableCount(spAtt.damage) > 0) then spAttDesc = spAttDesc..'<br/>(' local spAttDuration = spAttInterval * (spAtt.attackCount - 1) spAttDesc = spAttDesc..Shared.round(spAttInterval / 1000, 2, 2)..'s delay between attacks.' if spAtt.attackCount ~= nil and spAtt.attackCount > 2 then spAttDesc = spAttDesc..' '..Shared.round(spAttDuration / 1000, 2, 2)..'s total duration' end spAttDesc = spAttDesc..')' end table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAttDesc) 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 -- Generates a table showing the damage/DR multipliers for each combat triangle function p.getCombatTriangleTable() local triangleAttributes = { { ["name"] = 'damageModifier', ["head"] = 'DMG', ["func"] = function(val) local outVal = 100 * (val - 1) return { outVal, (outVal < 0 and '' or '+') .. string.format(outVal, '%.0f') .. '%' } end }, { ["name"] = 'reductionModifier', ["head"] = 'DR', ["func"] = function(val) return { (val - 1), string.format('%.2fx', val) } end } } local combatStyles = { { 'melee', Icons.Icon({ 'Attack', 'Melee', type = 'skill' }) }, { 'ranged', Icons.Icon({ 'Ranged', type = 'skill' }) }, { 'magic', Icons.Icon({ 'Magic', type = 'skill' }) } } local gameMode = { { 'Standard', 'Standard' }, { 'Hardcore', Icons.Icon({ 'Hardcore' }) } } local attrCount = Shared.tableCount(triangleAttributes) local styleCount = Shared.tableCount(combatStyles) local modeCount = Shared.tableCount(gameMode) local resultPart = {} -- Generate header table.insert(resultPart, '{| class="wikitable"\r\n|-') table.insert(resultPart, '\r\n!rowspan="2"| Player Style') table.insert(resultPart, '\r\n!rowspan="2"| Game Mode') for i, style in ipairs(combatStyles) do table.insert(resultPart, '\r\n!colspan="' .. attrCount .. '"| VS ' .. style[2]) end local attrHeader = '' for i, attr in ipairs(triangleAttributes) do attrHeader = attrHeader .. '\r\n! ' .. attr.head end table.insert(resultPart, '\r\n|-' .. string.rep(attrHeader, styleCount)) -- Generate table body for i, attStyle in ipairs(combatStyles) do local borderStyle = (i < styleCount and 'style="border-bottom:solid lightgrey"' or '') for j, mode in ipairs(gameMode) do table.insert(resultPart, '\r\n|-') if j == 1 then table.insert(resultPart, '\r\n|rowspan="' .. modeCount .. '" ' .. borderStyle .. '| ' .. attStyle[2]) elseif j == modeCount and borderStyle ~= '' then table.insert(resultPart, ' ' .. borderStyle) end table.insert(resultPart, '\r\n| ' .. mode[2]) for k, targStyle in ipairs(combatStyles) do for m, attr in ipairs(triangleAttributes) do local cellStyle = nil local attValRaw = Constants.getTriangleAttribute(attr.name, attStyle[1], targStyle[1], mode[1]) local attrVal = attr.func(attValRaw) if attrVal[1] > 0 then cellStyle = 'background-color:lightgreen;' elseif attrVal[1] < 0 then cellStyle = 'background-color:lightpink;' end table.insert(resultPart, '\r\n|' .. (cellStyle ~= nil and 'style="' .. cellStyle .. '"| ' or ' ') .. attrVal[2]) end end end end table.insert(resultPart, '\r\n|}') return table.concat(resultPart) end return p