Module:Magic: Difference between revisions

From Melvor Idle
(Okay for real this should be a fix for standard magic spell linking)
(Update for v1.1)
Line 1: Line 1:
local p = {}
local p = {}


local MagicData = mw.loadData('Module:Magic/data')
local Constants = require('Module:Constants')
local SkillData = mw.loadData('Module:Skills/data')
 
local Areas = require('Module:CombatAreas')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Attacks = require('Module:Attacks')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Constants = require('Module:Constants')


function processSpell(section, index)
p.spellBooks = {
local result = Shared.clone(MagicData[section][index])
    { id = 'standard', dataID = 'standardSpells', name = 'Standard Magic', imgType = 'spell' },
result.id = index - 1
    { id = 'ancient', dataID = 'ancientSpells', name = 'Ancient Magick', imgType = 'spell' },
result.type = section
    { id = 'archaic', dataID = 'archaicSpells', name = 'Archaic Magick', imgType = 'spell' },
return result
    { id = 'curse', dataID = 'curseSpells', name = 'Curse', imgType = 'curse' },
    { id = 'aurora', dataID = 'auroraSpells', name = 'Aurora', imgType = 'aurora' },
    { id = 'altMagic', dataID = 'altSpells', name = 'Alt. Magic', imgType = 'spell', dataBySkill = true }
}
 
p.spellBookIndex = {}
for i, spellBook in ipairs(p.spellBooks) do
    p.spellBookIndex[spellBook.id] = i
end
 
function p.getSpellBookID(sectionName)
    if sectionName == 'Spell' or sectionName == 'Standard' then
        return 'standard'
    elseif sectionName == 'Ancient' then
        return 'ancient'
    elseif sectionName == 'Archaic' then
        return 'archaic'
    elseif sectionName == 'Curse' then
        return 'curse'
    elseif sectionName == 'Aurora' then
        return 'aurora'
    elseif Shared.contains({'Alt Magic', 'Alt. Magic', 'Alternative Magic'}, sectionName) then
        return 'altMagic'
    else
        return sectionName
    end
end
end


function p.getSpell(name, type)
-- Retrieves all spells within the given spellbook
local section = type
function p.getSpellsBySpellBook(spellBookID)
if type == 'Spell' or type == 'Standard' then
    if type(spellBookID) == 'string' then
section = 'Spells'
        local spellBook = GameData.getEntityByID(p.spellBooks, spellBookID)
elseif type == 'Curse' then
        if spellBook ~= nil then
section = 'Curses'
            if spellBook.dataBySkill then
elseif type == 'Aurora' then
                -- Data is part of the Magic skill object
section = 'Auroras'
                local magicData = GameData.getSkillData('melvorD:Magic')
elseif type == 'Alt Magic' or type == 'Alternative Magic' then
                if magicData ~= nil then
section = 'AltMagic'
                    return magicData[spellBook.dataID]
end
                end
            else
                -- Data is at the root of GameData
                return GameData.rawData[spellBook.dataID]
            end
        end
    end
end


function p.getSpell(name, spellType)
local spellBookID = p.getSpellBookID(spellType)
name = Shared.fixPagename(name)
name = Shared.fixPagename(name)
if section == nil then
 
for i, section in Shared.skpairs(MagicData) do
    for i, spellBook in ipairs(p.spellBooks) do
for j, spell in Shared.skpairs(section) do
        if spellBookID == nil or spellBookID == spellBook.id then
if spell.name == name then
            local spells = p.getSpellsBySpellBook(spellBook.id)
return processSpell(i, j)
            local spell = GameData.getEntityByName(spells, name)
end
            if spell ~= nil then
end
                return spell
end
            end
elseif section ~= nil and MagicData[section] ~= nil then
        end
for i, spell in Shared.skpairs(MagicData[section]) do
    end
if spell.name == name then
return processSpell(section, i)
end
end
else
return nil
end
end
end


function p.getSpellByID(type, id)
function p.getSpellByID(id, spellType)
local section = type
local spellBookID = p.getSpellBookID(spellType)
if type == nil or type == 'Spell' or type == 'Standard' then
 
section = 'Spells'
    if spellType == nil or spellBookID ~= nil then
elseif type == 'Curse' then
        for i, spellBook in ipairs(p.spellBooks) do
section = 'Curses'
            if spellType == nil or spellBookID == spellBook.id then
elseif type == 'Aurora' then
                if spellBook.dataBySkill then
section = 'Auroras'
                    return GameData.getEntityByID(p.getSpellsBySpellBook(spellBook.id), id)
elseif type == 'Alt Magic' or type == 'Alternative Magic' then
                else
section = 'AltMagic'
                    return GameData.getEntityByID(spellBook.dataID, id)
end
                end
            end
        end
    end
end


if MagicData[section] ~= nil then
function p.getTypeString(spellType)
return processSpell(section, id + 1)
    local spellBookID = p.getSpellBookID(spellType)
else
    if spellBookID ~= nil then
return nil
        local spellBook = GameData.getEntityByID(p.spellBooks, spellBookID)
end
        if spellBook ~= nil then
            return spellBook.name
        end
    end
end
end


function p.getTypeString(type)
function p._getSpellIconType(spell)
if type == 'Auroras' then
    local spellBook = GameData.getEntityByID(p.spellBooks, spell.spellbook)
return 'Aurora'
    if spellBook == nil then
elseif type == 'Curses' then
        -- Pick a suitable default
return 'Curse'
        return 'spell'
elseif type == 'AltMagic' then
    else
return 'Alt. Magic'
        return spellBook.imgType
elseif type == 'Spells' then
    end
return "Combat Spell"
elseif type == 'Ancient' then
return 'Ancient Magick'
end
end
end


function p._getSpellIcon(spell, size)
function p._getSpellIcon(spell, size)
if size == nil then size = 50 end
if size == nil then size = 50 end
if spell.type == 'Auroras' then
    local imgType = p._getSpellIconType(spell)
return Icons.Icon({spell.name, type='aurora', notext=true, size=size})
    return Icons.Icon({spell.name, type=imgType, notext=true, size=size})
elseif spell.type == 'Curses' then
return Icons.Icon({spell.name, type='curse', notext=true, size=size})
else
return Icons.Icon({spell.name, type='spell', notext=true, size=size})
end
end
end


function p._getSpellRequirements(spell)
function p._getSpellRequirements(spell)
local result = ''
    -- All spells have a Magic level requirement
result = result..Icons._SkillReq('Magic', spell.level)
    local resultPart = { Icons._SkillReq('Magic', spell.level) }
if spell.requiredItem ~= nil and spell.requiredItem >= 0 then
 
local reqItem = Items.getItemByID(spell.requiredItem)
    if spell.itemRequiredID ~= nil then
result = result..'<br/>'..Icons.Icon({reqItem.name, type='item', notext=true})..' equipped'
        local item = Items.getItemByID(spell.itemRequiredID)
end
        if item ~= nil then
if spell.requiredDungeonCompletion ~= nil then
            table.insert(resultPart, Icons.Icon({item.name, type='item', notext=true}) .. ' Equipped')
local dung = Areas.getAreaByID('dungeon', spell.requiredDungeonCompletion[1])
        end
result = result..'<br/>'..Icons.Icon({dung.name, type='dungeon', notext=true, qty=spell.requiredDungeonCompletion[2]})..' Clears'
    end
end
    if spell.requirements ~= nil then
return result
        for i, req in ipairs(spell.requirements) do
            if req.type == 'DungeonCompletion' then
                local dung = GameData.getEntityByID('dungeons', req.dungeonID)
                if dung ~= nil then
                    table.insert(resultPart, Icons.Icon({dung.name, type='dungeon', qty=req.count, notext=true}) .. ' Clears')
                end
            elseif req.type == 'MonsterKilled' then
                local monster = GameData.getEntityByID('monsters', req.monsterID)
                if monster ~= nil then
                    table.insert(resultPart, Icons.Icon({monster.name, type='monster', qty=req.count, notext=true}) .. ' Kills')
                end
            else
                table.insert(resultPart, 'ERROR: Unknown requirement: ' .. (req.type or 'nil') .. '[[Category:Pages with script errors]]')
            end
        end
    end
 
    return table.concat(resultPart, '<br/>')
end
 
local function formatRuneList(runes)
    local runeList = {}
    for i, req in ipairs(runes) do
        local rune = Items.getItemByID(req.id)
        if rune ~= nil then
            table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
        end
    end
    return table.concat(runeList, ', ')
end
end


function p._getSpellRunes(spell)
function p._getSpellRunes(spell)
local formatRuneList = function(runes)
local runeList = {}
for i, req in ipairs(runes) do
local rune = Items.getItemByID(req.id)
if rune ~= nil then
table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
end
end
return table.concat(runeList, ', ')
end
if type(spell.runesRequired) == 'table' then
if type(spell.runesRequired) == 'table' then
local resultPart  = {}
local resultPart  = {}
Line 137: Line 179:
end
end
return p._getSpellRunes(spell)
return p._getSpellRunes(spell)
end
-- Generates description template data. See: altMagic.js, description()
function p._getSpellTemplateData(spell)
    local templateData = nil
    if spell.spellbook == 'altMagic' then
        if spell.produces ~= nil then
            -- Item produced varies depending on items consumed
            if spell.produces == 'Bar' then
                templateData = {
                    ["barAmount"] = spell.productionRatio,
                    ["oreAmount"] = spell.specialCost.quantity
                }
            elseif spell.produces == 'GP' then
                templateData = {
                    ["percent"] = spell.productionRatio * 100
                }
            else
                local itemProduced = Items.getItemByID(spell.produces)
                if itemProduced ~= nil and itemProduced.prayerPoints ~= nil then
                    -- Item produced is a bone
                    local costItem = Items.getItemByID(spell.fixedItemCosts[1].id)
                    if costItem ~= nil then
                        templateData = {
                            ["itemName"] = costItem.name,
                            ["qty1"] = spell.fixedItemCosts[0].quantity,
                            ["qty2"] = itemProduced.prayerPoints
                        }
                    end
                end
            end
        end
        if templateData == nil then
            templateData = {
                ["amount"] = spell.productionRatio,
                ["percent"] = spell.productionRatio * 100,
                ["specialCostQty"] = spell.specialCost.quantity
            }
            for i, fixedCost in ipairs(spell.fixedItemCosts) do
                local item = Items.getItemByID(fixedCost.id)
                if item ~= nil then
                    templateData['fixedItemName' .. (i - 1)] = item.name
                    templateData['fixedItemQty' .. (i - 1)] = fixedCost.quantity
                end
            end
        end
    end
    return (templateData or {})
end
end


function p._getSpellDescription(spell)
function p._getSpellDescription(spell)
local result = ''
if spell.description ~= nil then
if spell.description ~= nil then
if p.getSpellTypeIndex(spell.type) == 4 and string.find(spell.description, "<br>") ~= nil then
        return Shared.applyTemplateData(spell.description, p._getSpellTemplateData(spell))
result = string.sub(spell.description, 0, string.find(spell.description, "<br>")-1)
    elseif spell.modifiers ~= nil or spell.targetModifiers ~= nil then
else
        local resultPart = {}
result = spell.description
        if spell.modifiers ~= nil then
end
            table.insert(resultPart, Constants.getModifiersText(spell.modifiers, false))
elseif (spell.modifiers ~= nil or spell.enemyModifiers ~= nil) then
        end
if spell.modifiers ~= nil then
        if spell.targetModifiers ~= nil then
local playerModText = Constants.getModifiersText(spell.modifiers, false)
            local targetModText = Constants.getModifiersText(spell.targetModifiers, false)
result = playerModText
            table.insert(resultPart, 'Enemies are inflicted with:<br/>' .. targetModText)
end
        end
if spell.enemyModifiers ~= nil then
        return table.concat(resultPart, '<br/>')
local enemyModText = Constants.getModifiersText(spell.enemyModifiers, false)
    elseif spell.spellbook == 'standard' then
result = result .. (string.len(result) > 0 and '<br/>' or '') .. 'Enemies are inflicted with:<br/>' .. enemyModText
        return 'Combat spell with a max hit of ' .. Shared.formatnum(spell.maxHit * 10)
end
    else
elseif spell.type == 'Spells' then
        return ''
result = 'Combat spell with a max hit of '..(spell.maxHit * 10)
    end
end
 
return result
end
end


Line 175: Line 261:
return p._getSpellRunes(spell)
return p._getSpellRunes(spell)
elseif stat == 'type' then
elseif stat == 'type' then
return p.getTypeString(spell.type)
return p.getTypeString(spell.spellbook)
end
end
return spell[stat]
return spell[stat]
Line 207: Line 293:
function p._getSpellCategories(spell)
function p._getSpellCategories(spell)
local result = '[[Category:Spells]]'
local result = '[[Category:Spells]]'
result = result..'[[Category:'..p.getTypeString(spell.type)..']]'
result = result..'[[Category:'..p.getTypeString(spell.spellbook)..']]'
return result
return result
end
end
Line 220: Line 306:
end
end


-- If includeConsumes = true, then checks for Alt Magic spell resource consumptions as well as
function p._getAltSpellCostText(spell)
    if spell.specialCost ~= nil then
        local costType = spell.specialCost.type
        if costType == nil or costType == 'None' then
            if type(spell.fixedItemCosts) == 'table' then
                local costText = {}
                for i, itemCost in ipairs(spell.fixedItemCosts) do
                    local item = Items.getItemByID(itemCost.id)
                    if item ~= nil then
                        table.insert(Icons.Icon({item.name, type='item', qty=itemCost.quantity}))
                    end
                end
                if not Shared.tableIsEmpty(costText) then
                    return table.concat(costText, ', ')
                end
            else
                return nil
            end
        else
            local qty = Shared.formatnum(spell.specialCost.quantity)
            local typeString = {
                ['AnyItem'] = qty .. ' of any item',
                ['BarIngredientsWithCoal'] = qty .. ' x required ores for the chosen bar',
                ['BarIngredientsWithoutCoal'] = qty .. ' x required ores (except ' .. Icons.Icon({'Coal Ore', type='item'}) .. ') for the chosen bar',
                ['JunkItem'] = qty .. ' of any [[Fishing#Junk|Junk]] item',
                ['SuperiorGem'] = qty .. ' of any superior gem',
                ['AnyNormalFood'] = qty .. ' x non-perfect food'
            }
            return typeString[costType]
        end
    end
end
 
function p.getSpellsProducingItem(itemID)
    -- Only need to check Alt. Magic spells
    local spellList = {}
 
    -- Classify whether the item fits into various categories
    local isBar, isShard, isGem, isSuperiorGem, isPerfectFood = false, false, false, false, false
    local item = Items.getItemByID(itemID)
    if item ~= nil then
        isBar = item.type == 'Bar'
        isShard = GameData.getEntityByProperty(SkillData.Magic.randomShards, 'itemID', item.id) ~= nil
        isGem = GameData.getEntityByProperty('randomGems', 'itemID', itemID) ~= nil
        isSuperiorGem = item.type == 'Superior Gem'
        if item.healsFor ~= nil then
            -- Item is food, but is it a product of perfect cooking?
            local cookData = GameData.getSkillData('melvorD:Cooking')
            if cookData ~= nil and cookData.recipes ~= nil then
                isPerfectFood = GameData.getEntityByProperty(cookData.recipes, 'perfectCookID', itemID) ~= nil
            end
        end
    end
 
    for i, spell in ipairs(p.getSpellsBySpellBook('altMagic')) do
        local includeSpell = false
        if spell.produces ~= nil then
            if spell.produces == itemID then
                includeSpell = true
                break
            else
                includeSpell = ((isBar and spell.produces == 'Bar') or
                    (isShard and spell.produces == 'RandomShards') or
                    (isGem and spell.produces == 'RandomGem') or
                    (isSuperiorGem and spell.produces == 'RandomSuperiorGem') or
                    (isPerfectFood and spell.produces == 'PerfectFood'))
            end
            if includeSpell then
                table.insert(spellList, spell)
            end
        end
    end
 
    table.sort(spellList, function(a, b) return a.level < b.level end)
return spellList
end
 
-- If includeConsumes = true, then checks for Alt. Magic spell resource consumptions as well as
-- the rune cost of spells
-- the rune cost of spells
function p.getSpellsForItem(itemID, includeConsumes)
function p.getSpellsUsingItem(itemID, includeConsumes)
if type(includeConsumes) ~= 'boolean' then
if type(includeConsumes) ~= 'boolean' then
includeConsumes = false
includeConsumes = false
end
end
-- The superheat table is built later as & when needed
    local runeKeys = { 'runesRequired', 'runesRequiredAlt' }
local superheatCosts, coalID = nil, nil
    local spellList = {}
local spellList = {}
   
for secName, secArray in pairs(MagicData) do
    -- Initialize some vars & only populate if we're including resource consumptions
for i, spell in ipairs(secArray) do
    local isJunkItem, isSuperiorGem, isNormalFood, isCoal, isBarIngredient = false, false, false, false, false
local foundSpell = false
    if includeConsumes then
for j, req in pairs(spell.runesRequired) do
        local thisItem = Items.getItemByID(itemID)
if req.id == itemID then
        local junkItemIDs = GameData.getSkillData('melvorD:Fishing').junkItemIDs
foundSpell = true
        isJunkItem = Shared.contains(junkItemIDs, itemID)
break
        isSuperiorGem = thisItem.type == 'Superior Gem'
end
        if thisItem.healsFor ~= nil then
end
            -- Item is food, but is it from cooking & is it normal or perfect?
if not foundSpell and spell.runesRequiredAlt ~= nil then
            local cookData = GameData.getSkillData('melvorD:Cooking')
for j, req in pairs(spell.runesRequiredAlt) do
            if cookData ~= nil and cookData.recipes ~= nil then
if req.id == itemID then
                isNormalFood = GameData.getEntityByProperty(cookData.recipes, 'productID', itemID) ~= nil
foundSpell = true
            end
break
        end
end
        isCoal = itemID == 'melvorD:Coal_Ore'
end
        if not isCoal then
end
            -- Don't need to check if the item is another bar ingredient if we already know it is coal
if includeConsumes and not foundSpell and spell.consumes ~= nil and spell.consumes > 0 then
            local smithingRecipes = GameData.getSkillData('melvorD:Smithing').recipes
-- The consumes property of Alt Magic spells is as follows:
            for i, recipe in ipairs(smithingRecipes) do
-- 0 = Any item
                if recipe.categoryID == 'melvorD:Bars' then
-- 1 = Junk item
                    for k, itemCost in ipairs(recipe.itemCosts) do
-- 2 = Superheat/ores with Coal
                        if itemCost.id == itemID then
-- 3 = Superheat/ores without Coal
                            isBarIngredient = true
-- 4 = Nothing
                            break
-- 5 = Coal ore
                        end
-- We ignore 4 (no resource consumption) and 0 (as this would flag every item unhelpfully)
                    end
                    if isBarIngredient then
                        break
                    end
                end
            end
        end
    end
 
    -- Find applicable spells
    for i, spellBook in ipairs(p.spellBook) do
        local spells = p.getSpellsBySpellBook(spellBook.id)
        local foundSpell = false
        for j, spell in ipairs(spells) do
            -- Check runes first
            for k, runeKey in ipairs(runeKeys) do
                if spell[runeKey] ~= nil then
                    for m, req in ipairs(spell[runeKey]) do
                        if req.id == itemID then
                            foundSpell = true
                            break
                        end
                    end
                end
                if foundSpell then
                    break
                end
            end
            if includeConsumes and not foundSpell then
                -- Check items consumed by the spell
                -- Fixed costs first, as that is a well-defined list of item IDs
                if spell.fixedItemCosts ~= nil then
                    for k, itemCost in ipairs(spell.fixedItemCosts) do
                        if itemCost.id == itemID then
                            foundSpell = true
                            break
                        end
                    end
                end
                if not foundSpell and spell.specialCost ~= nil then
                    local costType = spell.specialCost.type
                    foundSpell = (isJunkItem and costType == 'JunkItem') or
                        (isSuperiorGem and costType == 'AnySuperiorGem') or
                        (isNormalFood and costType == 'AnyNormalFood') or
                        ((isCoal or isBarIngredient) and costType == 'BarIngredientsWithCoal') or
                        (isBarIngredient and costType == 'BarIngredientsWithoutCoal')
                end
            end
           
            if foundSpell then
                table.insert(spellList, spell)
            end
        end
    end


-- Determine the coal ID for the first time if we need it
    table.sort(spellList, function(a, b)
if coalID == nil and Shared.contains({2, 3, 5}, spell.consumes) then
        if a.spellBook ~= b.spellBook then
local coalItem = Items.getItem('Coal Ore')
            return p.spellBookIndex[a.spellBook] < p.spellBookIndex[b.spellBook]
if coalItem ~= nil then
        else
coalID = coalItem.id
            return a.level < b.level
end
        end
end
    end)
if spell.consumes == 1 and Shared.contains(SkillData.Fishing.JunkItems, itemID) then
foundSpell = true
elseif spell.consumes == 2 or spell.consumes == 3 then
if superheatCosts == nil then
-- Initialize the superheatItems table
superheatCosts = {}
for j, recipe in ipairs(SkillData.Smithing.Recipes) do
if recipe.category == 0 then
-- Bar recipe
for k, itemCost in ipairs(recipe.itemCosts) do
if itemCost.id ~= coalID then
superheatCosts[itemCost.id] = true
end
end
end
end
end
local ignoreCoal = (spell.consumes == 3)
if superheatCosts[itemID] or (not ignoreCoal and itemID == coalID) then
foundSpell = true
end
elseif spell.consumes == 5 and itemID == coalID then
foundSpell = true
end
end
if foundSpell then
table.insert(spellList, processSpell(secName, i))
end
end
end
table.sort(spellList, function(a, b)
if a.type ~= b.type then
return p.getSpellTypeIndex(a.type) < p.getSpellTypeIndex(b.type)
else
return a.level < b.level
end
end)
return spellList
return spellList
end
end
Line 305: Line 484:
-- The below function is included for backwards compatibility
-- The below function is included for backwards compatibility
function p.getSpellsForRune(runeID)
function p.getSpellsForRune(runeID)
return p.getSpellsForItem(runeID, false)
return p.getSpellsUsingItem(runeID, false)
end
end


function p.getSpellTypeIndex(type)
function p.getSpellTypeLink(spellBookID)
if type == 'Spells' then
    if spellBookID == 'standard' then
return 0
elseif type == 'Curses' then
return 1
elseif type == 'Auroras' then
return 2
elseif type == 'Ancient' then
return 3
elseif type == 'AltMagic' then
return 4
end
return -1
end
 
function p.getSpellTypeLink(type)
if type == 'Spells' then
return Icons.Icon({'Standard Magic', 'Standard', img='Standard', type='spellType'})
return Icons.Icon({'Standard Magic', 'Standard', img='Standard', type='spellType'})
elseif type == 'Curses' then
    elseif spellBookID == 'ancient' then
        return Icons.Icon({'Ancient Magicks', 'Ancient', img='Ancient', type='spellType'})
    elseif spellBookID == 'archaic' then
        return Icons.Icon({'Archaic Magicks', 'Archaic', img='Archaic', type='spellType'})
    elseif spellBookID == 'curse' then
return Icons.Icon({'Curses', 'Curse', img='Curse', type='spellType'})
return Icons.Icon({'Curses', 'Curse', img='Curse', type='spellType'})
elseif type == 'Auroras' then
    elseif spellBookID == 'aurora' then
return Icons.Icon({'Auroras', 'Aurora', img='Aurora', type='spellType'})
return Icons.Icon({'Auroras', 'Aurora', img='Aurora', type='spellType'})
elseif type == 'Ancient' then
    elseif spellBookID == 'altMagic' then
return Icons.Icon({'Ancient Magicks', 'Ancient', img='Ancient', type='spellType'})
elseif type == 'AltMagic' then
return Icons.Icon({'Alt. Magic', type='skill'})
return Icons.Icon({'Alt. Magic', type='skill'})
end
end
Line 339: Line 505:


function p._getSpellRow(spell, includeTypeColumn)
function p._getSpellRow(spell, includeTypeColumn)
local rowTxt = '\r\n|-\r\n|data-sort-value="'..spell.name..'"|'
    local spellBookIdx = p.spellBookIndex[spell.spellBook]
if spell.type == 'Auroras' then
    local spellBook = p.spellBooks[spellBookIdx]
rowTxt = rowTxt..Icons.Icon({spell.name, type='aurora', notext=true, size=50})
    local rowPart = {'\r\n|-\r\n|data-sort-value="' .. spell.name .. '"| '}
elseif spell.type == 'Curses' then
    table.insert(rowPart, Icons.Icon({spell.name, type=spellBook.imgType, notext=true, size=50}))
rowTxt = rowTxt..Icons.Icon({spell.name, type='curse', notext=true, size=50})
table.insert(rowPart, '|| ' .. Icons.Icon({spell.name, type=spellBook.imgType, noicon=true}))
else
table.insert(rowPart, '||data-sort-value="' .. spell.level .. '"| ' .. p._getSpellRequirements(spell))
rowTxt = rowTxt..Icons.Icon({spell.name, type='spell', notext=true, size=50})
end
rowTxt = rowTxt..'||'..Icons.Icon({spell.name, type='spell', noicon=true})
rowTxt = rowTxt..'||data-sort-value="'..spell.level..'"|'..Icons._SkillReq('Magic', spell.level)
--Handle required items/dungeon clears
if spell.requiredItem ~= nil and spell.requiredItem >= 0 then
local reqItem = Items.getItemByID(spell.requiredItem)
rowTxt = rowTxt..'<br/>'..Icons.Icon({reqItem.name, type='item', notext=true})..' equipped'
end
if spell.requiredDungeonCompletion ~= nil then
local dung = Areas.getAreaByID('dungeon', spell.requiredDungeonCompletion[1])
rowTxt = rowTxt..'<br/>'..Icons.Icon({dung.name, type='dungeon', notext=true, qty=spell.requiredDungeonCompletion[2]})..' Clears'
end


if includeTypeColumn then
if includeTypeColumn then
rowTxt = rowTxt..'||data-sort-value="'..p.getSpellTypeIndex(spell.type)..'"|'
table.insert(rowPart, '||data-sort-value="' .. spellBookIdx .. '"| ' .. p.getSpellTypeLink(spell.spellBook))
rowTxt = rowTxt..p.getSpellTypeLink(spell.type)
end
end
--8/20/21: Changed to just getting the spell's description outright
--8/20/21: Changed to just getting the spell's description outright
rowTxt = rowTxt..'||'..p._getSpellStat(spell, 'description')
table.insert(rowPart, '|| ' .. p._getSpellStat(spell, 'description'))
--1/4/22: haha just kidding. Now we're also getting delay between attacks for spells with special attacks
--1/4/22: haha just kidding. Now we're also getting delay between attacks for spells with special attacks
if spell.specialAttack ~= nil then
if spell.specialAttack ~= nil then
local spAtt = spell.specialAttack
local spAtt = Attacks.getAttackByID(spell.specialAttack)
local interval = spAtt.attackInterval ~= nil and spAtt.attackInterval or -1
local interval = spAtt.attackInterval
if interval ~= -1 then
if interval ~= nil then
local hits = spAtt.attackCount ~= nil and spAtt.attackCount or 1
local hits = spAtt.attackCount ~= nil and spAtt.attackCount or 1
rowTxt = rowTxt..'<br/>('
            table.insert(rowPart, '<br/>(' .. Shared.round(interval / 1000, 2, 2) .. 's delay between attacks.')
rowTxt = rowTxt..Shared.round(interval / 1000, 2, 2)..'s delay between attacks.'
if hits > 2 then
if hits > 2 then
rowTxt = rowTxt..' '..Shared.round(interval * (hits - 1) / 1000, 2, 2)..'s total duration.'
table.insert(rowPart, ' ' .. Shared.round(interval * (hits - 1) / 1000, 2, 2) .. 's total duration.')
end
end
rowTxt = rowTxt..')'
table.insert(rowPart, ')')
end
end
end
end
if p.getSpellTypeIndex(spell.type) == 4 then
if spell.spellBook == 'altMagic' then
rowTxt = rowTxt..'||'..spell.baseExperience
table.insert(rowPart, '|| ' .. spell.baseExperience)
end
end
rowTxt = rowTxt..'||style="text-align:center"|'
table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellRunes(spell))
rowTxt = rowTxt..p._getSpellRunes(spell)
return table.concat(rowPart)
return rowTxt
end
 
function p._getSpellBookTable(spellBookID)
    local spells = p.getSpellsBySpellBook(spellBookID)
    if spells ~= nil and not Shared.tableIsEmpty(spells) then
        local resultPart = {}
        table.insert(resultPart, '{|class="wikitable sortable"\r\n!colspan="2"| Spell')
        table.insert(resultPart, '\r\n! Requirements')
        table.insert(resultPart, '\r\n!style="width:275px"| Description')
        if spellBookID == 'altMagic' then
            table.insert(resultPart, '\r\n! Experience')
        end
        table.insert(resultPart, '\r\n! Runes')
 
        for i, spell in ipairs(spells) do
            table.insert(resultPart, p._getSpellRow(spell, false))
        end
 
        table.insert(resultPart, '\r\n|}')
        return table.concat(resultPart)
    end
end
end


-- Included below for backwards compatibility
function p.getStandardSpellsTable(frame)
function p.getStandardSpellsTable(frame)
local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
    return p._getSpellBookTable('standard')
result = result..'!!Requirements'
end
result = result..'!!style="width:275px"|Description'
 
result = result..'!!Runes'
function p.getAncientTable(frame)
local spellList = {}
    return p._getSpellBookTable('ancient')
for i, spell in Shared.skpairs(MagicData.Spells) do
local rowTxt = p._getSpellRow(processSpell('Spells', i), false)
result = result..rowTxt
end
result = result..'\r\n|}'
return result
end
end


function p.getCurseTable(frame)
function p.getCurseTable(frame)
local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
    return p._getSpellBookTable('curse')
result = result..'!!Requirements'
result = result..'!!style="width:275px"|Description'
result = result..'!!Runes'
local spellList = {}
for i, spell in Shared.skpairs(MagicData.Curses) do
local rowTxt = p._getSpellRow(processSpell('Curses', i), false)
result = result..rowTxt
end
result = result..'\r\n|}'
return result
end
end


function p.getAuroraTable(frame)
function p.getAuroraTable(frame)
local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
    return p._getSpellBookTable('aurora')
result = result..'!!Requirements'
result = result..'!!style="width:275px"|Description'
result = result..'!!Runes'
for i, spell in Shared.skpairs(MagicData.Auroras) do
local rowTxt = p._getSpellRow(processSpell('Auroras', i), false)
result = result..rowTxt
end
result = result..'\r\n|}'
return result
end
 
function p.getAncientTable(frame)
local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
result = result..'!!Requirements'
result = result..'!!style="width:275px"|Description'
result = result..'!!Runes'
for i, spell in Shared.skpairs(MagicData.Ancient) do
local rowTxt = p._getSpellRow(processSpell('Ancient', i), false)
result = result..rowTxt
end
result = result..'\r\n|}'
return result
end
end


function p.getAltSpellsTable(frame)
function p.getAltSpellsTable(frame)
local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
    return p._getSpellBookTable('altMagic')
result = result..'!!Requirements'
result = result..'!!style="width:275px"|Description'
result = result..'!!Experience'
result = result..'!!Runes'
local spellList = {}
for i, spell in Shared.skpairs(MagicData.AltMagic) do
local rowTxt = p._getSpellRow(processSpell('AltMagic', i), false)
result = result..rowTxt
end
result = result..'\r\n|}'
return result
end
end


return p
return p

Revision as of 15:51, 22 October 2022

Data pulled from Module:GameData/data


local p = {}

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Attacks = require('Module:Attacks')
local Icons = require('Module:Icons')
local Items = require('Module:Items')

p.spellBooks = {
    { id = 'standard', dataID = 'standardSpells', name = 'Standard Magic', imgType = 'spell' },
    { id = 'ancient', dataID = 'ancientSpells', name = 'Ancient Magick', imgType = 'spell' },
    { id = 'archaic', dataID = 'archaicSpells', name = 'Archaic Magick', imgType = 'spell' },
    { id = 'curse', dataID = 'curseSpells', name = 'Curse', imgType = 'curse' },
    { id = 'aurora', dataID = 'auroraSpells', name = 'Aurora', imgType = 'aurora' },
    { id = 'altMagic', dataID = 'altSpells', name = 'Alt. Magic', imgType = 'spell', dataBySkill = true }
}

p.spellBookIndex = {}
for i, spellBook in ipairs(p.spellBooks) do
    p.spellBookIndex[spellBook.id] = i
end

function p.getSpellBookID(sectionName)
    if sectionName == 'Spell' or sectionName == 'Standard' then
        return 'standard'
    elseif sectionName == 'Ancient' then
        return 'ancient'
    elseif sectionName == 'Archaic' then
        return 'archaic'
    elseif sectionName == 'Curse' then
        return 'curse'
    elseif sectionName == 'Aurora' then
        return 'aurora'
    elseif Shared.contains({'Alt Magic', 'Alt. Magic', 'Alternative Magic'}, sectionName) then
        return 'altMagic'
    else
        return sectionName
    end
end

-- Retrieves all spells within the given spellbook
function p.getSpellsBySpellBook(spellBookID)
    if type(spellBookID) == 'string' then
        local spellBook = GameData.getEntityByID(p.spellBooks, spellBookID)
        if spellBook ~= nil then
            if spellBook.dataBySkill then
                -- Data is part of the Magic skill object
                local magicData = GameData.getSkillData('melvorD:Magic')
                if magicData ~= nil then
                    return magicData[spellBook.dataID]
                end
            else
                -- Data is at the root of GameData
                return GameData.rawData[spellBook.dataID]
            end
        end
    end
end

function p.getSpell(name, spellType)
	local spellBookID = p.getSpellBookID(spellType)
	name = Shared.fixPagename(name)

    for i, spellBook in ipairs(p.spellBooks) do
        if spellBookID == nil or spellBookID == spellBook.id then
            local spells = p.getSpellsBySpellBook(spellBook.id)
            local spell = GameData.getEntityByName(spells, name)
            if spell ~= nil then
                return spell
            end
        end
    end
end

function p.getSpellByID(id, spellType)
	local spellBookID = p.getSpellBookID(spellType)

    if spellType == nil or spellBookID ~= nil then
        for i, spellBook in ipairs(p.spellBooks) do
            if spellType == nil or spellBookID == spellBook.id then
                if spellBook.dataBySkill then
                    return GameData.getEntityByID(p.getSpellsBySpellBook(spellBook.id), id)
                else
                    return GameData.getEntityByID(spellBook.dataID, id)
                end
            end
        end
    end
end

function p.getTypeString(spellType)
    local spellBookID = p.getSpellBookID(spellType)
    if spellBookID ~= nil then
        local spellBook = GameData.getEntityByID(p.spellBooks, spellBookID)
        if spellBook ~= nil then
            return spellBook.name
        end
    end
end

function p._getSpellIconType(spell)
    local spellBook = GameData.getEntityByID(p.spellBooks, spell.spellbook)
    if spellBook == nil then
        -- Pick a suitable default
        return 'spell'
    else
        return spellBook.imgType
    end
end

function p._getSpellIcon(spell, size)
	if size == nil then size = 50 end
    local imgType = p._getSpellIconType(spell)
    return Icons.Icon({spell.name, type=imgType, notext=true, size=size})
end

function p._getSpellRequirements(spell)
    -- All spells have a Magic level requirement
    local resultPart = { Icons._SkillReq('Magic', spell.level) }

    if spell.itemRequiredID ~= nil then
        local item = Items.getItemByID(spell.itemRequiredID)
        if item ~= nil then
            table.insert(resultPart, Icons.Icon({item.name, type='item', notext=true}) .. ' Equipped')
        end
    end
    if spell.requirements ~= nil then
        for i, req in ipairs(spell.requirements) do
            if req.type == 'DungeonCompletion' then
                local dung = GameData.getEntityByID('dungeons', req.dungeonID)
                if dung ~= nil then
                    table.insert(resultPart, Icons.Icon({dung.name, type='dungeon', qty=req.count, notext=true}) .. ' Clears')
                end
            elseif req.type == 'MonsterKilled' then
                local monster = GameData.getEntityByID('monsters', req.monsterID)
                if monster ~= nil then
                    table.insert(resultPart, Icons.Icon({monster.name, type='monster', qty=req.count, notext=true}) .. ' Kills')
                end
            else
                table.insert(resultPart, 'ERROR: Unknown requirement: ' .. (req.type or 'nil') .. '[[Category:Pages with script errors]]')
            end
        end
    end

    return table.concat(resultPart, '<br/>')
end

local function formatRuneList(runes)
    local runeList = {}
    for i, req in ipairs(runes) do
        local rune = Items.getItemByID(req.id)
        if rune ~= nil then
            table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
        end
    end
    return table.concat(runeList, ', ') 
end

function p._getSpellRunes(spell)
	if type(spell.runesRequired) == 'table' then
		local resultPart  = {}
		table.insert(resultPart, formatRuneList(spell.runesRequired))
		if spell.runesRequiredAlt ~= nil and not Shared.tablesEqual(spell.runesRequired, spell.runesRequiredAlt) then
			table.insert(resultPart, "<br/>'''OR'''<br/>" .. formatRuneList(spell.runesRequiredAlt))
		end
		return table.concat(resultPart)
	else
		return ''
	end
end

function p.getSpellRunes(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return "ERROR: No spell named "..spellName.." exists in the data module"
	end
	return p._getSpellRunes(spell)
end

-- Generates description template data. See: altMagic.js, description()
function p._getSpellTemplateData(spell)
    local templateData = nil
    if spell.spellbook == 'altMagic' then
        if spell.produces ~= nil then
            -- Item produced varies depending on items consumed
            if spell.produces == 'Bar' then
                templateData = {
                    ["barAmount"] = spell.productionRatio,
                    ["oreAmount"] = spell.specialCost.quantity
                }
            elseif spell.produces == 'GP' then
                templateData = {
                    ["percent"] = spell.productionRatio * 100
                }
            else
                local itemProduced = Items.getItemByID(spell.produces)
                if itemProduced ~= nil and itemProduced.prayerPoints ~= nil then
                    -- Item produced is a bone
                    local costItem = Items.getItemByID(spell.fixedItemCosts[1].id)
                    if costItem ~= nil then
                        templateData = {
                            ["itemName"] = costItem.name,
                            ["qty1"] = spell.fixedItemCosts[0].quantity,
                            ["qty2"] = itemProduced.prayerPoints
                        }
                    end
                end
            end
        end
        if templateData == nil then
            templateData = {
                ["amount"] = spell.productionRatio,
                ["percent"] = spell.productionRatio * 100,
                ["specialCostQty"] = spell.specialCost.quantity
            }
            for i, fixedCost in ipairs(spell.fixedItemCosts) do
                 local item = Items.getItemByID(fixedCost.id)
                 if item ~= nil then
                    templateData['fixedItemName' .. (i - 1)] = item.name
                    templateData['fixedItemQty' .. (i - 1)] = fixedCost.quantity
                 end
            end
        end
    end
    return (templateData or {})
end

function p._getSpellDescription(spell)
	if spell.description ~= nil then
        return Shared.applyTemplateData(spell.description, p._getSpellTemplateData(spell))
    elseif spell.modifiers ~= nil or spell.targetModifiers ~= nil then
        local resultPart = {}
        if spell.modifiers ~= nil then
            table.insert(resultPart, Constants.getModifiersText(spell.modifiers, false))
        end
        if spell.targetModifiers ~= nil then
            local targetModText = Constants.getModifiersText(spell.targetModifiers, false)
            table.insert(resultPart, 'Enemies are inflicted with:<br/>' .. targetModText)
        end
        return table.concat(resultPart, '<br/>')
    elseif spell.spellbook == 'standard' then
        return 'Combat spell with a max hit of ' .. Shared.formatnum(spell.maxHit * 10)
    else
        return ''
    end
end

function p._getSpellStat(spell, stat)
	if stat == 'bigIcon' then
		return p._getSpellIcon(spell, 250)
	elseif stat == 'description' then
		return p._getSpellDescription(spell)
	elseif stat == 'icon' then
		return p._getSpellIcon(spell)
	elseif stat == 'requirements' then
		return p._getSpellRequirements(spell)
	elseif stat == 'runes' then
		return p._getSpellRunes(spell)
	elseif stat == 'type' then
		return p.getTypeString(spell.spellbook)
	end
	return spell[stat]
end

function p.getSpellStat(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame[1]
	local statName = frame.args ~= nil and frame.args[2] or frame[2]
	local spell = p.getSpell(spellName)
	if spell == nil then
		return "ERROR: No spell named "..spellName.." found"
	end
	return p._getSpellStat(spell, statName)
end

function p.getOtherSpellBoxText(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return "ERROR: No spell named "..spellName.." found"
	end

	local result = ''

	--8/20/21: Changed to using the new getSpellDescription function
	result = result.."\r\n|-\r\n|'''Description:'''<br/>"..p._getSpellStat(spell, 'description')

	return result
end

function p._getSpellCategories(spell)
	local result = '[[Category:Spells]]'
	result = result..'[[Category:'..p.getTypeString(spell.spellbook)..']]'
	return result
end

function p.getSpellCategories(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return "ERROR: No spell named "..spellName.." found"
	end
	return p._getSpellCategories(spell)
end

function p._getAltSpellCostText(spell)
    if spell.specialCost ~= nil then
        local costType = spell.specialCost.type
        if costType == nil or costType == 'None' then
            if type(spell.fixedItemCosts) == 'table' then
                local costText = {}
                for i, itemCost in ipairs(spell.fixedItemCosts) do
                    local item = Items.getItemByID(itemCost.id)
                    if item ~= nil then
                        table.insert(Icons.Icon({item.name, type='item', qty=itemCost.quantity}))
                    end
                end
                if not Shared.tableIsEmpty(costText) then
                    return table.concat(costText, ', ')
                end
            else
                return nil
            end
        else
            local qty = Shared.formatnum(spell.specialCost.quantity)
            local typeString = {
                ['AnyItem'] = qty .. ' of any item',
                ['BarIngredientsWithCoal'] = qty .. ' x required ores for the chosen bar',
                ['BarIngredientsWithoutCoal'] = qty .. ' x required ores (except ' .. Icons.Icon({'Coal Ore', type='item'}) .. ') for the chosen bar',
                ['JunkItem'] = qty .. ' of any [[Fishing#Junk|Junk]] item',
                ['SuperiorGem'] = qty .. ' of any superior gem',
                ['AnyNormalFood'] = qty .. ' x non-perfect food'
            }
            return typeString[costType]
        end
    end
end

function p.getSpellsProducingItem(itemID)
    -- Only need to check Alt. Magic spells
    local spellList = {}

    -- Classify whether the item fits into various categories
    local isBar, isShard, isGem, isSuperiorGem, isPerfectFood = false, false, false, false, false
    local item = Items.getItemByID(itemID)
    if item ~= nil then
        isBar = item.type == 'Bar'
        isShard = GameData.getEntityByProperty(SkillData.Magic.randomShards, 'itemID', item.id) ~= nil
        isGem = GameData.getEntityByProperty('randomGems', 'itemID', itemID) ~= nil
        isSuperiorGem = item.type == 'Superior Gem'
        if item.healsFor ~= nil then
            -- Item is food, but is it a product of perfect cooking?
            local cookData = GameData.getSkillData('melvorD:Cooking')
            if cookData ~= nil and cookData.recipes ~= nil then
                isPerfectFood = GameData.getEntityByProperty(cookData.recipes, 'perfectCookID', itemID) ~= nil
            end
        end
    end

    for i, spell in ipairs(p.getSpellsBySpellBook('altMagic')) do
        local includeSpell = false
        if spell.produces ~= nil then
            if spell.produces == itemID then
                includeSpell = true
                break
            else
                includeSpell = ((isBar and spell.produces == 'Bar') or
                    (isShard and spell.produces == 'RandomShards') or
                    (isGem and spell.produces == 'RandomGem') or
                    (isSuperiorGem and spell.produces == 'RandomSuperiorGem') or
                    (isPerfectFood and spell.produces == 'PerfectFood'))
            end
            if includeSpell then
                table.insert(spellList, spell)
            end
        end
    end

    table.sort(spellList, function(a, b) return a.level < b.level end)
	return spellList
end

-- If includeConsumes = true, then checks for Alt. Magic spell resource consumptions as well as
-- the rune cost of spells
function p.getSpellsUsingItem(itemID, includeConsumes)
	if type(includeConsumes) ~= 'boolean' then
		includeConsumes = false
	end
    local runeKeys = { 'runesRequired', 'runesRequiredAlt' }
    local spellList = {}
    
    -- Initialize some vars & only populate if we're including resource consumptions
    local isJunkItem, isSuperiorGem, isNormalFood, isCoal, isBarIngredient = false, false, false, false, false
    if includeConsumes then
        local thisItem = Items.getItemByID(itemID)
        local junkItemIDs = GameData.getSkillData('melvorD:Fishing').junkItemIDs
        isJunkItem = Shared.contains(junkItemIDs, itemID)
        isSuperiorGem = thisItem.type == 'Superior Gem'
        if thisItem.healsFor ~= nil then
            -- Item is food, but is it from cooking & is it normal or perfect?
            local cookData = GameData.getSkillData('melvorD:Cooking')
            if cookData ~= nil and cookData.recipes ~= nil then
                isNormalFood = GameData.getEntityByProperty(cookData.recipes, 'productID', itemID) ~= nil
            end
        end
        isCoal = itemID == 'melvorD:Coal_Ore'
        if not isCoal then
            -- Don't need to check if the item is another bar ingredient if we already know it is coal
            local smithingRecipes = GameData.getSkillData('melvorD:Smithing').recipes
            for i, recipe in ipairs(smithingRecipes) do
                if recipe.categoryID == 'melvorD:Bars' then
                    for k, itemCost in ipairs(recipe.itemCosts) do
                        if itemCost.id == itemID then
                            isBarIngredient = true
                            break
                        end
                    end
                    if isBarIngredient then
                        break
                    end
                end
            end
        end
    end

    -- Find applicable spells
    for i, spellBook in ipairs(p.spellBook) do
        local spells = p.getSpellsBySpellBook(spellBook.id)
        local foundSpell = false
        for j, spell in ipairs(spells) do
            -- Check runes first
            for k, runeKey in ipairs(runeKeys) do
                if spell[runeKey] ~= nil then
                    for m, req in ipairs(spell[runeKey]) do
                        if req.id == itemID then
                            foundSpell = true
                            break
                        end
                    end
                end
                if foundSpell then
                    break
                end
            end
            if includeConsumes and not foundSpell then
                -- Check items consumed by the spell
                -- Fixed costs first, as that is a well-defined list of item IDs
                if spell.fixedItemCosts ~= nil then
                    for k, itemCost in ipairs(spell.fixedItemCosts) do
                        if itemCost.id == itemID then
                            foundSpell = true
                            break
                        end
                    end
                end
                if not foundSpell and spell.specialCost ~= nil then
                    local costType = spell.specialCost.type
                    foundSpell = (isJunkItem and costType == 'JunkItem') or
                        (isSuperiorGem and costType == 'AnySuperiorGem') or
                        (isNormalFood and costType == 'AnyNormalFood') or
                        ((isCoal or isBarIngredient) and costType == 'BarIngredientsWithCoal') or
                        (isBarIngredient and costType == 'BarIngredientsWithoutCoal')
                end
            end
            
            if foundSpell then
                table.insert(spellList, spell)
            end
        end
    end

    table.sort(spellList, function(a, b)
        if a.spellBook ~= b.spellBook then
            return p.spellBookIndex[a.spellBook] < p.spellBookIndex[b.spellBook]
        else
            return a.level < b.level
        end
    end)
	return spellList
end

-- The below function is included for backwards compatibility
function p.getSpellsForRune(runeID)
	return p.getSpellsUsingItem(runeID, false)
end

function p.getSpellTypeLink(spellBookID)
    if spellBookID == 'standard' then
		return Icons.Icon({'Standard Magic', 'Standard', img='Standard', type='spellType'})
    elseif spellBookID == 'ancient' then
        return Icons.Icon({'Ancient Magicks', 'Ancient', img='Ancient', type='spellType'})
    elseif spellBookID == 'archaic' then
        return Icons.Icon({'Archaic Magicks', 'Archaic', img='Archaic', type='spellType'})
    elseif spellBookID == 'curse' then
		return Icons.Icon({'Curses', 'Curse', img='Curse', type='spellType'})
    elseif spellBookID == 'aurora' then
		return Icons.Icon({'Auroras', 'Aurora', img='Aurora', type='spellType'})
    elseif spellBookID == 'altMagic' then
		return Icons.Icon({'Alt. Magic', type='skill'})
	end
	return ''
end

function p._getSpellRow(spell, includeTypeColumn)
    local spellBookIdx = p.spellBookIndex[spell.spellBook]
    local spellBook = p.spellBooks[spellBookIdx]
    local rowPart = {'\r\n|-\r\n|data-sort-value="' .. spell.name .. '"| '}
    table.insert(rowPart, Icons.Icon({spell.name, type=spellBook.imgType, notext=true, size=50}))
	table.insert(rowPart, '|| ' .. Icons.Icon({spell.name, type=spellBook.imgType, noicon=true}))
	table.insert(rowPart, '||data-sort-value="' .. spell.level .. '"| ' .. p._getSpellRequirements(spell))

	if includeTypeColumn then
		table.insert(rowPart, '||data-sort-value="' .. spellBookIdx .. '"| ' .. p.getSpellTypeLink(spell.spellBook))
	end
	--8/20/21: Changed to just getting the spell's description outright
	table.insert(rowPart, '|| ' .. p._getSpellStat(spell, 'description'))
	--1/4/22: haha just kidding. Now we're also getting delay between attacks for spells with special attacks
	if spell.specialAttack ~= nil then
		local spAtt = Attacks.getAttackByID(spell.specialAttack)
		local interval = spAtt.attackInterval
		if interval ~= nil then
			local hits = spAtt.attackCount ~= nil and spAtt.attackCount or 1
            table.insert(rowPart, '<br/>(' .. Shared.round(interval / 1000, 2, 2) .. 's delay between attacks.')
			if hits > 2 then
				table.insert(rowPart, ' ' .. Shared.round(interval * (hits - 1) / 1000, 2, 2) .. 's total duration.')
			end
			table.insert(rowPart, ')')
		end
	end
	if spell.spellBook == 'altMagic' then
		table.insert(rowPart, '|| ' .. spell.baseExperience)
	end
	table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellRunes(spell))
	return table.concat(rowPart)
end

function p._getSpellBookTable(spellBookID)
    local spells = p.getSpellsBySpellBook(spellBookID)
    if spells ~= nil and not Shared.tableIsEmpty(spells) then
        local resultPart = {}
        table.insert(resultPart, '{|class="wikitable sortable"\r\n!colspan="2"| Spell')
        table.insert(resultPart, '\r\n! Requirements')
        table.insert(resultPart, '\r\n!style="width:275px"| Description')
        if spellBookID == 'altMagic' then
            table.insert(resultPart, '\r\n! Experience')
        end
        table.insert(resultPart, '\r\n! Runes')

        for i, spell in ipairs(spells) do
            table.insert(resultPart, p._getSpellRow(spell, false))
        end

        table.insert(resultPart, '\r\n|}')
        return table.concat(resultPart)
    end
end

-- Included below for backwards compatibility
function p.getStandardSpellsTable(frame)
    return p._getSpellBookTable('standard')
end

function p.getAncientTable(frame)
    return p._getSpellBookTable('ancient')
end

function p.getCurseTable(frame)
    return p._getSpellBookTable('curse')
end

function p.getAuroraTable(frame)
    return p._getSpellBookTable('aurora')
end

function p.getAltSpellsTable(frame)
    return p._getSpellBookTable('altMagic')
end

return p