2,869
edits
(Don't return table when number of attacks is zero) |
No edit summary |
||
(17 intermediate revisions by 2 users not shown) | |||
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 GameData = require('Module:GameData') | |||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Attacks = require('Module:Attacks') | local Attacks = require('Module:Attacks') | ||
local Num = require('Module:Number') | |||
function p. | function p._getSpecialAttackTable(effectDefn, categories, sourceHeaderLabel, includeSource) | ||
-- TODO needs fixing for V1.3 | |||
-- Is going to be incredibly broken following combat effects & modifiers overhaul | |||
return '' | |||
--[==[ | |||
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 | |||
-- | -- Build index so we can tell which to include later | ||
for i, | local includedAttacks = {} | ||
for i, attack in ipairs(attacks) do | |||
includedAttacks[attack.id] = true | |||
end | |||
-- Compile a list of monsters, items, spells, etc. for each included attack | |||
-- Monsters | |||
local overrideChance = (monster.overrideSpecialChances ~= nil and Shared. | if includeCat['Monster'] then | ||
for i, monster in ipairs(GameData.rawData.monsters) do | |||
if monster.specialAttacks ~= nil and not Shared.tableIsEmpty(monster.specialAttacks) then | |||
local overrideChance = (monster.overrideSpecialChances ~= nil and not Shared.tableIsEmpty(monster.overrideSpecialChances)) | |||
for j, spAttID in ipairs(monster.specialAttacks) do | |||
local | if includedAttacks[spAttID] then | ||
if spAtt | local spAtt = GameData.getEntityByID('attacks', spAttID) | ||
if spAtt ~= nil then | |||
local attChance = (overrideChance and monster.overrideSpecialChances[j]) or spAtt.defaultChance | |||
table.insert(spAttTable, { id = spAttID, source = 'Monster', sourceSort = monster.name, sourceText = Icons.Icon({ monster.name, type = 'monster' }), chance = attChance, descType = 'monster' }) | |||
end | end | ||
end | end | ||
end | end | ||
end | |||
end | |||
end | |||
-- Items/Weapons | |||
if includeCat['Item'] then | |||
for i, item in ipairs(GameData.rawData.items) do | |||
if item.specialAttacks ~= nil and not item.golbinRaidExclusive and not Shared.tableIsEmpty(item.specialAttacks) then | |||
local overrideChance = (item.overrideSpecialChances ~= nil and not Shared.tableIsEmpty(item.overrideSpecialChances)) | |||
for j, spAttID in ipairs(item.specialAttacks) do | |||
if includedAttacks[spAttID] then | |||
local spAtt = GameData.getEntityByID('attacks', spAttID) | |||
if spAtt ~= nil then | |||
local attChance = (overrideChance and item.overrideSpecialChances[j]) or spAtt.defaultChance | |||
table.insert(spAttTable, { id = spAttID, source = 'Weapon', sourceSort = item.name, sourceText = Icons.Icon({ item.name, type = 'item' }), chance = attChance, descType = 'player' }) | |||
end | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
-- | -- Spells | ||
if | if includeCat['Spell'] then | ||
local spellCats = { 'ancientSpells', 'archaicSpells' } | |||
for i, spellCat in ipairs(spellCats) do | |||
for j, spell in ipairs(GameData.rawData[spellCat]) do | |||
local spAttID = spell.specialAttack or spell.specialAttackID | |||
if spAttID ~= nil and includedAttacks[spAttID] then | |||
local spAtt = GameData.getEntityByID('attacks', spAttID) | |||
if spAtt ~= nil then | |||
table.insert(spAttTable, {id = spAtt.id, source = 'Spell', sourceSort = spell.name, sourceText = Icons.Icon({ spell.name, type = 'spell' }), chance = spAtt.defaultChance, descType = 'player' }) | |||
end | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
-- Summoning familiars. Any effects inflicted by combat familiars aren't actually special | |||
end | -- attacks, therefore the handling here is a bit different | ||
if includeCat['Familiar'] then | |||
local famIdx = Shared.tableCount(attacks) + 1 | |||
local familiars = Items.getItems( | |||
function(item) | |||
if item.type == 'Familiar' and item.modifiers ~= nil and Items._getItemStat(item, 'summoningMaxhit') ~= nil then | |||
local famAttack = { prehitEffects = {}, onhitEffects = { { type = 'Modifier', subtype = 'Familiar', modifiers = item.modifiers } } } | |||
if effectDefn == nil then | |||
return Shared.tableIsEmpty(Attacks.getAttackEffects(famAttack)) | |||
else | |||
return Attacks.attackHasEffect(famAttack, effectDefn) | |||
end | |||
end | |||
return false | |||
end) | |||
for i, 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, { id = familiar.id, 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] = { id = familiar.id, name = familiar.name .. ' (Familiar)', description = famDesc } | |||
famIdx = famIdx + 1 | |||
end | |||
end | |||
-- Nothing to output if there are no row definitions | |||
if Shared.tableIsEmpty(spAttTable) 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 = { 'id', '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 id, dt, chance = rowDefn.id, rowDefn.descType, rowDefn.chance | |||
if rowCounts[id] == nil then | |||
rowCounts[id] = { rows = 0 } | |||
end | |||
if rowCounts[id][dt] == nil then | |||
rowCounts[id][dt] = { rows = 0 } | |||
end | |||
if rowCounts[id][dt][chance] == nil then | |||
rowCounts[id][dt][chance] = 0 | |||
end | |||
rowCounts[id]['rows'] = rowCounts[id]['rows'] + 1 | |||
rowCounts[id][dt]['rows'] = rowCounts[id][dt]['rows'] + 1 | |||
rowCounts[id][dt][chance] = rowCounts[id][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 = { id = true, descType = true, chance = true } | |||
local prevRowVal = { id = 0, descType = '', chance = 0 } | |||
local resetOnChange = { | |||
id = { 'id', 'descType', 'chance' }, | |||
descType = { 'descType', 'chance' }, | |||
chance = { 'chance' } | |||
} | |||
local rowSuffix = '' | |||
for i, spAttRow in ipairs(spAttTable) do | |||
local spIdx = spAttRow.id | |||
local spAtt = GameData.getEntityByID(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.id 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;"| ' .. Num.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 | |||
--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..Num.round(spAttInterval / 1000, 2, 2)..'s delay between attacks.' | |||
if spAtt.attackCount ~= nil and spAtt.attackCount > 2 then | |||
spAttDesc = spAttDesc..' '..Num.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 Shared.printError('Invalid effect name "' .. effectName .. '", must be one of: ' .. table.concat(validEffectNames, ', ')) | |||
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() | |||
-- TODO needs fixing for V1.3 | |||
return '' | |||
--[==[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 Mode', 'Standard' }, | |||
{ 'Hardcore Mode', 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 | end | ||
return p | return p |
edits