Module:Magic: Difference between revisions

From Melvor Idle
(fixed an error when getting spell descriptions sometimes)
m (Fix typo)
 
(46 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


local MagicData = mw.loadData('Module:Magic/data')
local Constants = require('Module:Constants')
 
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 Common = require('Module:Common')
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
 
-- 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
 
--Returns the expansion icon for the spell if it has one
function p.getExpansionIcon(frame)
local spellName = frame.args ~= nil and frame.args[1] or frame
local spell = p.getSpell(spellName)
if spell == nil then
return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
end
return Icons.getExpansionIcon(spell.id)
end
end


function p.getSpell(name, type)
function p.getTypeString(spellType)
  local section = type
local spellBookID = p.getSpellBookID(spellType)
  if type == 'Spell' or type == 'Standard' then
if spellBookID ~= nil then
    section = 'Spells'
local spellBook = GameData.getEntityByID(p.spellBooks, spellBookID)
  elseif type == 'Curse' then
if spellBook ~= nil then
    section = 'Curses'
return spellBook.name
  elseif type == 'Aurora' then
end
    section = 'Auroras'
end
  elseif type == 'Alt Magic' or type == 'Alternative Magic' then
    section='AltMagic'
  end
 
  if section == nil then
    for i, section in Shared.skpairs(MagicData) do
      for j, spell in Shared.skpairs(section) do
        if spell.name == name then
          return processSpell(i, j)
        end
      end
    end
  elseif section ~= nil and MagicData[section] ~= nil then
    for i, spell in Shared.skpairs(MagicData[section]) do
      if spell.name == name then
        return processSpell(section, i)
      end
    end
  else
    return nil
  end
end
end


function p.getSpellByID(type, id)
function p._getSpellIconType(spell)
  local section = type
local spellBook = GameData.getEntityByID(p.spellBooks, spell.spellBook)
  if type == nil or type == 'Spell' or type == 'Standard' then
if spellBook == nil then
    section = 'Spells'
-- Pick a suitable default
  elseif type == 'Curse' then
return 'spell'
    section = 'Curses'
else
  elseif type == 'Aurora' then
return spellBook.imgType
    section = 'Auroras'
end
  elseif type == 'Alt Magic' or type == 'Alternative Magic' then
    section='AltMagic'
  end
 
  if MagicData[section] ~= nil then
    return processSpell(section, id + 1)
  else
    return nil
  end
end
end


function p.getTypeString(type)
function p.getSpellIconType(frame)
  if type == 'Auroras' then
local spellName = frame.args ~= nil and frame.args[1] or frame
    return 'Aurora'
local spell = p.getSpell(spellName)
  elseif type == 'Curses' then
if spell == nil then
    return 'Curse'
return 'spell'
  elseif type == 'AltMagic' then
else
    return 'Alt. Magic'
return p._getSpellIconType(spell)
  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 extraReqs = {
  if spell.requiredItem ~= nil and spell.requiredItem >= 0 then
{ ['type'] = 'SkillLevel', ['skillID'] = 'melvorD:Magic', ['level'] = spell.level }
    local reqItem = Items.getItemByID(spell.requiredItem)
}
    result = result..'<br/>'..Icons.Icon({reqItem.name, type='item', notext=true})..' equipped'
if spell.requiredItemID ~= nil then
  end
table.insert(extraReqs, { ['type'] = 'SlayerItem', ['itemID'] = spell.requiredItemID })
  if spell.requiredDungeonCompletion ~= nil then
end
    local dung = Areas.getAreaByID('dungeon', spell.requiredDungeonCompletion[1])
 
    result = result..'<br/>'..Icons.Icon({dung.name, type='dungeon', notext=true, qty=spell.requiredDungeonCompletion[2]})..' Clears'
local resultPart = {}
  end
for i, reqs in ipairs({ extraReqs, spell.requirements }) do
  return result
local reqStr = Common.getRequirementString(reqs)
if reqStr ~= nil then
table.insert(resultPart, reqStr)
end
end
 
if Shared.tableIsEmpty(resultPart) then
return 'None'
else
return table.concat(resultPart, '<br/>')
end
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.quantity}))
end
end
return table.concat(runeList, ', ')
end
 
 
function p._getSpellItems(spell)
if type(spell.fixedItemCosts) == 'table' then
local resultPart = {}
for i, req in ipairs(spell.fixedItemCosts) do
local item = Items.getItemByID(req.id)
if item ~= nil then
table.insert(resultPart, Icons.Icon({item.name, type='item', qty = req.quantity}))
end
end
return table.concat(resultPart, '<br/>')
else
return ''
end
end
 
function p.getSpellItems(frame)
local spellName = frame.args ~= nil and frame.args[1] or frame
local spell = p.getSpell(spellName)
if spell == nil then
return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
end
return p._getSpellItems(spell)
end
end


function p._getSpellRunes(spell)
function p._getSpellRunes(spell)
  local result = ''
if type(spell.runesRequired) == 'table' then
  for i, req in Shared.skpairs(spell.runesRequired) do
local resultPart  = {}
    local rune = Items.getItemByID(req.id)
table.insert(resultPart, formatRuneList(spell.runesRequired))
    if i > 1 then result = result..', ' end
if spell.runesRequiredAlt ~= nil and not Shared.tablesEqual(spell.runesRequired, spell.runesRequiredAlt) then
    result = result..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
table.insert(resultPart, "<br/>'''OR'''<br/>" .. formatRuneList(spell.runesRequiredAlt))
  end
end
  if spell.runesRequiredAlt ~= nil and not Shared.tablesEqual(spell.runesRequired, spell.runesRequiredAlt) then
return table.concat(resultPart)
    result = result.."<br/>'''OR'''<br/>"
else
    for i, req in pairs(spell.runesRequiredAlt) do
return ''
      local rune = Items.getItemByID(req.id)
end
      if i > 1 then result = result..', ' end
      result = result..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
    end
  end
  return result
end
end


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


function p._getSpellDescription(spell)
-- Generates description template data. See: altMagic.js, description()
  local result = ''
function p._getSpellTemplateData(spell)
  if spell.description ~= nil then
local templateData = nil
    if p.getSpellTypeIndex(spell.type) == 4 and string.find(spell.description, "<br>") ~= nil then
if spell.spellBook == 'altMagic' then
      result = string.sub(spell.description, 0, string.find(spell.description, "<br>")-1)
if spell.produces ~= nil then
    else
-- Item produced varies depending on items consumed
      result = spell.description
if spell.produces == 'Bar' then
    end
templateData = {
  elseif spell.modifiers ~= nil then
["barAmount"] = spell.productionRatio,
    result = Constants.getModifiersText(spell.modifiers, false)
["oreAmount"] = spell.specialCost.quantity
  elseif spell.type == 'Spells' then
}
    result = 'Combat spell with a max hit of '..(spell.maxHit * 10)
elseif spell.produces == 'GP' then
  end
templateData = {
["percent"] = spell.productionRatio * 100
}
else
local itemProduced = Items.getItemByID(spell.produces)
local spellNS, spellLocalID = GameData.getLocalID(spell.id)
if itemProduced ~= nil and itemProduced.prayerPoints ~= nil and type(spell.fixedItemCosts) == 'table' and Shared.tableCount(spell.fixedItemCosts) == 1 and spellNS ~= 'melvorAoD' then
-- Item produced is a bone and spell is not from AoD (logic from altMagic.js)
local costItem = Items.getItemByID(spell.fixedItemCosts[1].id)
if costItem ~= nil then
templateData = {
["itemName"] = costItem.name,
["qty1"] = spell.fixedItemCosts[1].quantity,
["qty2"] = itemProduced.prayerPoints
}
end
end
end
end
if templateData == nil then
templateData = {
["amount"] = spell.productionRatio,
["percent"] = spell.productionRatio * 100,
["specialCostQty"] = spell.specialCost.quantity
}
if type(spell.fixedItemCosts) == 'table' then
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
end
return (templateData or {})
end


  return result
function p._getSpellDescription(spell, inline)
if inline == nil then inline = false end
local connector = inline and '<br/>' or ' and '
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, inline)
if inline then
table.insert(resultPart, targetModText)
else
table.insert(resultPart, 'Enemies are inflicted with:<br/>' .. targetModText)
end
end
return table.concat(resultPart, connector)
elseif spell.specialAttackID ~= nil or spell.specialAttack ~= nil then
local spAtt = Attacks.getAttackByID(spell.specialAttackID or spell.specialAttack)
if spAtt ~= nil then
return spAtt.description
end
elseif spell.spellBook == 'standard' then
return 'Combat spell with a max hit of ' .. Shared.formatnum(spell.maxHit * 10)
else
return ''
end
end
end


function p._getSpellStat(spell, stat)
function p._getSpellStat(spell, stat)
  if stat == 'bigIcon' then
if stat == 'bigIcon' then
    return p._getSpellIcon(spell, 250)
return p._getSpellIcon(spell, 250)
  elseif stat == 'description' then
elseif stat == 'description' then
    return p._getSpellDescription(spell)
return p._getSpellDescription(spell)
  elseif stat == 'icon' then
elseif stat == 'icon' then
    return p._getSpellIcon(spell)
return p._getSpellIcon(spell)
  elseif stat == 'requirements' then
elseif stat == 'requirements' then
    return p._getSpellRequirements(spell)
return p._getSpellRequirements(spell)
  elseif stat == 'runes' then
elseif stat == 'runes' then
    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
elseif stat == 'spellDamage' then
  return spell[stat]
if spell.maxHit ~= nil then
return spell.maxHit * 10
else
return 0
end
end
return spell[stat]
end
end


function p.getSpellStat(frame)
function p.getSpellStat(frame)
  local spellName = frame.args ~= nil and frame.args[1] or frame[1]
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 statName = frame.args ~= nil and frame.args[2] or frame[2]
  local spell = p.getSpell(spellName)
local spell = p.getSpell(spellName)
  if spell == nil then
if spell == nil then
    return "ERROR: No spell named "..spellName.." found"
return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
  end
end
  return p._getSpellStat(spell, statName)
return p._getSpellStat(spell, statName)
end
end


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


  local result = ''
local result = ''


  --8/20/21: Changed to using the new getSpellDescription function
--11/01/22: Added Spell Damage for standard & archaic spells
  result = result.."\r\n|-\r\n|'''Description:'''<br/>"..p._getSpellStat(spell, 'description')
if spell.spellBook == 'standard' or spell.spellBook == 'archaic' then
result = result.."\r\n|-\r\n|'''Spell Damage:''' "..p._getSpellStat(spell, 'spellDamage')
end
--8/20/21: Changed to using the new getSpellDescription function
result = result.."\r\n|-\r\n|'''Description:'''<br/>"..p._getSpellStat(spell, 'description')


  return result
return result
end
end


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


function p.getSpellCategories(frame)
function p.getSpellCategories(frame)
  local spellName = frame.args ~= nil and frame.args[1] or frame
local spellName = frame.args ~= nil and frame.args[1] or frame
  local spell = p.getSpell(spellName)
local spell = p.getSpell(spellName)
  if spell == nil then
if spell == nil then
    return "ERROR: No spell named "..spellName.." found"
return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
  end
end
  return p._getSpellCategories(spell)
return p._getSpellCategories(spell)
end
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(costText, 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 = not Shared.tableIsEmpty(GameData.getEntities(SkillData.Smithing.recipes,
function(recipe)
return recipe.categoryID == 'melvorD:Bars' and recipe.productID == item.id
end))
isShard = GameData.getEntityByProperty(SkillData.Magic.randomShards, 'itemID', item.id) ~= nil
isGem = GameData.getEntityByProperty('randomGems', 'itemID', itemID) ~= nil
--Runestone can't be created by Alt Magic spells that make random superior gems.
isSuperiorGem = item.type == 'Superior Gem' and item.id ~= SkillData.Mining.runestoneItemID
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
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.spellBooks) do
local spells = p.getSpellsBySpellBook(spellBook.id)
for j, spell in ipairs(spells) do
local foundSpell = false
-- 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)
function p.getSpellsForRune(runeID)
  local spellList = {}
return p.getSpellsUsingItem(runeID, false)
  for secName, secArray in Shared.skpairs(MagicData) do
    for i, spell in pairs(secArray) do
      local foundSpell = false
      for j, req in pairs(spell.runesRequired) do
        if req.id == runeID then
          table.insert(spellList, processSpell(secName, i))
          foundSpell = true
          break
        end
      end
      if spell.runesRequiredAlt ~= nil and not foundSpell then
        for j, req in pairs(spell.runesRequiredAlt) do
          if req.id == runeID then
            table.insert(spellList, processSpell(secName, i))
            break
          end
        end
      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
end
end


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


function p.getSpellTypeLink(type)
function p._getSpellHeader(includeTypeColumn, includeItems, includeDamage, includeExperience)
  if type == 'Spells' then
local resultPart = {}
    return Icons.Icon({'Magic', 'Standard', img='Standard', type='spellType'})
table.insert(resultPart, '{|class="wikitable sortable"\n!colspan="2"| Spell')
  elseif type == 'Curses' then
if includeTypeColumn then
    return Icons.Icon({'Curses', 'Curse', img='Curse', type='spellType'})
table.insert(resultPart, 'Spellbook')
  elseif type == 'Auroras' then
end
    return Icons.Icon({'Auroras', 'Aurora', img='Aurora', type='spellType'})
table.insert(resultPart, 'Requirements')
  elseif type == 'Ancient' then
if includeDamage then
    return Icons.Icon({'Ancient Magicks', 'Ancient', img='Ancient', type='spellType'})
table.insert(resultPart, 'Spell Dmg')
  elseif type == 'AltMagic' then
end
    return Icons.Icon({'Alt. Magic', type='skill'})
table.insert(resultPart, 'style="width:275px"| Description')
  end
if includeExperience then
  return ''
table.insert(resultPart, 'XP')
end
table.insert(resultPart, 'style="min-width:90px"| Runes')
if includeItems then
table.insert(resultPart, 'Item Cost')
end
return table.concat(resultPart, '\n! ')
end
end


function p._getSpellRow(spell, includeTypeColumn)
function p._getSpellRow(spell, includeTypeColumn, includeItems, includeDamage, includeExperience)
  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 = {'\n|-\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.getExpansionIcon(spell.id) .. Icons.Icon({spell.name, type=spellBook.imgType, noicon=true}))
  else
if includeTypeColumn then
    rowTxt = rowTxt..Icons.Icon({spell.name, type='spell', notext=true, size=50})
table.insert(rowPart, '||data-sort-value="' .. spellBookIdx .. '"| ' .. p.getSpellTypeLink(spell.spellBook))
  end
end
  rowTxt = rowTxt..'||[['..spell.name..']]'
table.insert(rowPart, '||data-sort-value="' .. spell.level .. '"| ' .. p._getSpellRequirements(spell))
  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
--11/01/22: Added base damage if requested
    rowTxt = rowTxt..'||data-sort-value="'..p.getSpellTypeIndex(spell.type)..'"|'
if includeDamage then
    rowTxt = rowTxt..p.getSpellTypeLink(spell.type)
local dmg = p._getSpellStat(spell, 'spellDamage')
  end
table.insert(rowPart, (dmg > 0 and '||style="text-align:right"| ' .. dmg or '||class="table-na"| N/A'))
  --8/20/21: Changed to just getting the spell's description outright
end
  rowTxt = rowTxt..'||'..p._getSpellStat(spell, 'description')
  if p.getSpellTypeIndex(spell.type) == 4 then
--8/20/21: Changed to just getting the spell's description outright
    rowTxt = rowTxt..'||'..spell.magicXP
table.insert(rowPart, '|| ' .. p._getSpellStat(spell, 'description'))
  end
--1/4/22: haha just kidding. Now we're also getting delay between attacks for spells with special attacks
  rowTxt = rowTxt..'||style="text-align:center"|'
local spAttID = spell.specialAttackID or spell.specialAttack
  rowTxt = rowTxt..p._getSpellRunes(spell)
if spAttID ~= nil then
  return rowTxt
local spAtt = Attacks.getAttackByID(spAttID)
local interval = spAtt.attackInterval
local hits = spAtt.attackCount ~= nil and spAtt.attackCount or 1
if interval ~= nil and hits > 1 then
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 includeExperience then
local xp = spell.baseExperience
table.insert(rowPart, ((xp == nil or xp == 0) and '||class="table-na"| N/A' or '||style="text-align:right"| ' .. xp))
end
table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellRunes(spell))
if includeItems then
table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellItems(spell))
end
return table.concat(rowPart)
end
end


function p._getSpellTable(spellList, includeTypeColumn)
if type(spellList) == 'table' and not Shared.tableIsEmpty(spellList) then
local includeSpellbook, includeItems, includeDamage, includeExperience = false, false, false, false
if type(includeTypeColumn) == 'boolean' then
includeSpellbook = includeTypeColumn
end
-- Check to see what columns are required
for i, spell in ipairs(spellList) do
if not includeItems and p._getSpellItems(spell) ~= '' then
includeItems = true
end
if not includeExperience and spell.spellBook == 'altMagic' then
includeExperience = true
end
if not includeDamage and (spell.spellBook == 'archaic' or spell.spellBook == 'standard') then
includeDamage = true
end
end
local spellListSorted = Shared.shallowClone(spellList)
table.sort(spellListSorted, function(a, b) return a.level < b.level end)
local resultPart = {p._getSpellHeader(includeSpellbook, includeItems, includeDamage, includeExperience)}
for i, spell in ipairs(spellListSorted) do
table.insert(resultPart, p._getSpellRow(spell, includeSpellbook, includeItems, includeDamage, includeExperience))
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
end
function p.getSpellTableFromList(frame)
local args = frame.args ~= nil and frame.args or frame
local spellListText = args[1]
local includeSpellbook = args.includeSpellbook ~= nil and string.lower(args.includeSpellbook) == 'true'
local spellNames = Shared.splitString(spellListText, ',')
local spellList = {}
for i, spellName in ipairs(spellNames) do
local spell = p.getSpell(spellName)
if spell == nil then
return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
else
table.insert(spellList, spell)
end
end
return p._getSpellTable(spellList, includeSpellbook)
end
function p.getSpellBookTable(frame)
local spellBook = frame.args ~= nil and frame.args[1] or frame[1]
spellBook = p.getSpellBookID(spellBook)
return p._getSpellTable(p.getSpellsBySpellBook(spellBook), false)
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._getSpellTable(p.getSpellsBySpellBook('standard'), false)
  result = result..'!!Requirements'
end
  result = result..'!!style="width:275px"|Description'
 
  result = result..'!!Runes'
function p.getAncientTable(frame)
  local spellList = {}
return p._getSpellTable(p.getSpellsBySpellBook('ancient'), false)
  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._getSpellTable(p.getSpellsBySpellBook('curse'), false)
  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._getSpellTable(p.getSpellsBySpellBook('aurora'), false)
  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._getSpellTable(p.getSpellsBySpellBook('altMagic'), false)
  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

Latest revision as of 09:46, 15 November 2023

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 Common = require('Module:Common')
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

--Returns the expansion icon for the spell if it has one
function p.getExpansionIcon(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
	end
	
	return Icons.getExpansionIcon(spell.id)
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.getSpellIconType(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return 'spell'
	else
		return p._getSpellIconType(spell)
	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 extraReqs = {
		{ ['type'] = 'SkillLevel', ['skillID'] = 'melvorD:Magic', ['level'] = spell.level }
	}
	if spell.requiredItemID ~= nil then
		table.insert(extraReqs, { ['type'] = 'SlayerItem', ['itemID'] = spell.requiredItemID })
	end

	local resultPart = {}
	for i, reqs in ipairs({ extraReqs, spell.requirements }) do
		local reqStr = Common.getRequirementString(reqs)
		if reqStr ~= nil then
			table.insert(resultPart, reqStr)
		end
	end

	if Shared.tableIsEmpty(resultPart) then
		return 'None'
	else
		return table.concat(resultPart, '<br/>')
	end
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.quantity}))
		end
	end
	return table.concat(runeList, ', ') 
end


function p._getSpellItems(spell)
	if type(spell.fixedItemCosts) == 'table' then
		local resultPart = {}
		for i, req in ipairs(spell.fixedItemCosts) do
			local item = Items.getItemByID(req.id)
			if item ~= nil then
				table.insert(resultPart, Icons.Icon({item.name, type='item', qty = req.quantity}))
			end
		end
		return table.concat(resultPart, '<br/>')
	else
		return ''
	end
end

function p.getSpellItems(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = p.getSpell(spellName)
	if spell == nil then
		return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
	end
	return p._getSpellItems(spell)
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 Shared.printError('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)
				local spellNS, spellLocalID = GameData.getLocalID(spell.id)
				if itemProduced ~= nil and itemProduced.prayerPoints ~= nil and type(spell.fixedItemCosts) == 'table' and Shared.tableCount(spell.fixedItemCosts) == 1 and spellNS ~= 'melvorAoD' then
					-- Item produced is a bone and spell is not from AoD (logic from altMagic.js)
					local costItem = Items.getItemByID(spell.fixedItemCosts[1].id)
					if costItem ~= nil then
						templateData = {
							["itemName"] = costItem.name,
							["qty1"] = spell.fixedItemCosts[1].quantity,
							["qty2"] = itemProduced.prayerPoints
						}
					end
				end
			end
		end
		if templateData == nil then
			templateData = {
				["amount"] = spell.productionRatio,
				["percent"] = spell.productionRatio * 100,
				["specialCostQty"] = spell.specialCost.quantity
			}
			if type(spell.fixedItemCosts) == 'table' then
				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
	end
	return (templateData or {})
end

function p._getSpellDescription(spell, inline)
	if inline == nil then inline = false end
	local connector = inline and '<br/>' or ' and '
	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, inline)
			if inline then 
				table.insert(resultPart, targetModText)
			else
				table.insert(resultPart, 'Enemies are inflicted with:<br/>' .. targetModText)
			end
		end
		return table.concat(resultPart, connector)
	elseif spell.specialAttackID ~= nil or spell.specialAttack ~= nil then
		local spAtt = Attacks.getAttackByID(spell.specialAttackID or spell.specialAttack)
		if spAtt ~= nil then
			return spAtt.description
		end
	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)
	elseif stat == 'spellDamage' then
		if spell.maxHit ~= nil then 
			return spell.maxHit * 10
		else
			return 0
		end
	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 Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
	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 Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
	end

	local result = ''

	--11/01/22: Added Spell Damage for standard & archaic spells
	if spell.spellBook == 'standard' or spell.spellBook == 'archaic' then
		result = result.."\r\n|-\r\n|'''Spell Damage:''' "..p._getSpellStat(spell, 'spellDamage')
	end
	--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 Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
	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(costText, 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 = not Shared.tableIsEmpty(GameData.getEntities(SkillData.Smithing.recipes,
				function(recipe)
					return recipe.categoryID == 'melvorD:Bars' and recipe.productID == item.id
				end))
		isShard = GameData.getEntityByProperty(SkillData.Magic.randomShards, 'itemID', item.id) ~= nil
		isGem = GameData.getEntityByProperty('randomGems', 'itemID', itemID) ~= nil
		--Runestone can't be created by Alt Magic spells that make random superior gems.
		isSuperiorGem = item.type == 'Superior Gem' and item.id ~= SkillData.Mining.runestoneItemID
		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
			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.spellBooks) do
		local spells = p.getSpellsBySpellBook(spellBook.id)
		for j, spell in ipairs(spells) do
			local foundSpell = false
			-- 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._getSpellHeader(includeTypeColumn, includeItems, includeDamage, includeExperience)
	local resultPart = {}
	table.insert(resultPart, '{|class="wikitable sortable"\n!colspan="2"| Spell')
	if includeTypeColumn then
		table.insert(resultPart, 'Spellbook')
	end
	table.insert(resultPart, 'Requirements')
	if includeDamage then
		table.insert(resultPart, 'Spell Dmg')
	end
	table.insert(resultPart, 'style="width:275px"| Description')
	if includeExperience then
		table.insert(resultPart, 'XP')
	end
	table.insert(resultPart, 'style="min-width:90px"| Runes')
	if includeItems then
		table.insert(resultPart, 'Item Cost')
	end
	return table.concat(resultPart, '\n! ')
end

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

	--11/01/22: Added base damage if requested
	if includeDamage then
		local dmg = p._getSpellStat(spell, 'spellDamage')
		table.insert(rowPart, (dmg > 0 and '||style="text-align:right"| ' .. dmg or '||class="table-na"| N/A'))
	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
	local spAttID = spell.specialAttackID or spell.specialAttack
	if spAttID ~= nil then
		local spAtt = Attacks.getAttackByID(spAttID)
		local interval = spAtt.attackInterval
		local hits = spAtt.attackCount ~= nil and spAtt.attackCount or 1
		if interval ~= nil and hits > 1 then
			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 includeExperience then
		local xp = spell.baseExperience
		table.insert(rowPart, ((xp == nil or xp == 0) and '||class="table-na"| N/A' or '||style="text-align:right"| ' .. xp))
	end
	table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellRunes(spell))
	if includeItems then
		table.insert(rowPart, '||style="text-align:center"| ' .. p._getSpellItems(spell))
	end
	return table.concat(rowPart)
end

function p._getSpellTable(spellList, includeTypeColumn)
	if type(spellList) == 'table' and not Shared.tableIsEmpty(spellList) then
		local includeSpellbook, includeItems, includeDamage, includeExperience = false, false, false, false
		if type(includeTypeColumn) == 'boolean' then
			includeSpellbook = includeTypeColumn
		end
		-- Check to see what columns are required
		for i, spell in ipairs(spellList) do
			if not includeItems and p._getSpellItems(spell) ~= '' then
				includeItems = true
			end
			if not includeExperience and spell.spellBook == 'altMagic' then
				includeExperience = true
			end
			if not includeDamage and (spell.spellBook == 'archaic' or spell.spellBook == 'standard') then
				includeDamage = true
			end
		end

		local spellListSorted = Shared.shallowClone(spellList)
		table.sort(spellListSorted, function(a, b) return a.level < b.level end)
		local resultPart = {p._getSpellHeader(includeSpellbook, includeItems, includeDamage, includeExperience)}
		for i, spell in ipairs(spellListSorted) do
			table.insert(resultPart, p._getSpellRow(spell, includeSpellbook, includeItems, includeDamage, includeExperience))
		end

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

function p.getSpellTableFromList(frame)
	local args = frame.args ~= nil and frame.args or frame
	local spellListText = args[1]
	local includeSpellbook = args.includeSpellbook ~= nil and string.lower(args.includeSpellbook) == 'true'
	local spellNames = Shared.splitString(spellListText, ',')
	local spellList = {}
	for i, spellName in ipairs(spellNames) do
		local spell = p.getSpell(spellName)
		if spell == nil then
			return Shared.printError('No spell named "' .. spellName .. '" exists in the data module')
		else
			table.insert(spellList, spell)
		end
	end
	return p._getSpellTable(spellList, includeSpellbook)
end

function p.getSpellBookTable(frame)
	local spellBook = frame.args ~= nil and frame.args[1] or frame[1]
	spellBook = p.getSpellBookID(spellBook)
	return p._getSpellTable(p.getSpellsBySpellBook(spellBook), false)
end

-- Included below for backwards compatibility
function p.getStandardSpellsTable(frame)
	return p._getSpellTable(p.getSpellsBySpellBook('standard'), false)
end

function p.getAncientTable(frame)
	return p._getSpellTable(p.getSpellsBySpellBook('ancient'), false)
end

function p.getCurseTable(frame)
	return p._getSpellTable(p.getSpellsBySpellBook('curse'), false)
end

function p.getAuroraTable(frame)
	return p._getSpellTable(p.getSpellsBySpellBook('aurora'), false)
end

function p.getAltSpellsTable(frame)
	return p._getSpellTable(p.getSpellsBySpellBook('altMagic'), false)
end

return p