Module:Skills/Summoning: Difference between revisions

From Melvor Idle
(Fix Summoning non-shard item cost quantity)
m (Add function to get a familiar recipe by id)
 
(33 intermediate revisions by 3 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Skills = require('Module:Skills')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Shop = require('Module:Shop')
local Num = require('Module:Number')


function p.getFamiliars()
 
  return Items.getItems(function(item) return item.type == "Familiar" end)
-- Gets a familiar recipe by ID, e.g. melvorF:GolbinThief
function p.getFamiliarRecipeByID(id)
return GameData.getEntityByID(SkillData.Summoning.recipes, id)
end
end


function p.getMarkTable(frame)
function p.getSynergies(checkFunc)
  local result = ''
local result = {}
  result = result..'{| class="wikitable sortable stickyHeader"'
for i, synergy in pairs(SkillData.Summoning.synergies) do
  result = result..'\r\n|- class="headerRow-0"'
if checkFunc(synergy) then
  result = result..'\r\n!colspan="2"|Mark!!'..Icons.Icon({'Summoning', type='skill', notext=true})..' Level'
table.insert(result, synergy)
  result = result..'!!Discovered in'
end
end
return result
end


  local Familiars = {}
local function getSummonModifierText(summonItem, maxVisible)
  for i, recipe in ipairs(SkillData.Summoning.Marks) do
local playerMods = Modifiers.getModifiersText(summonItem.modifiers, false, false, maxVisible) or ''
  table.insert(Familiars, recipe)
local enemyMods = Modifiers.getModifiersText(summonItem.enemyModifiers, false, false, maxVisible, function(text) return 'Gives the enemy: ' .. text end) or ''
  end
return playerMods .. (playerMods ~= '' and '<br>' or '') .. enemyMods
  table.sort(Familiars, function(a, b)
end
                          if a.level == b.level then
                            return a.masteryID < b.masteryID
                          else
                            return a.level < b.level
                          end
                        end)


  local rowArray = {}
function p.getMarkTable(frame)
  for i, Fam in ipairs(Familiars) do
local args = frame.args ~= nil and frame.args or frame
local item = Items.getItemByID(Fam.itemID)
local realmName = args.realm
if item ~= nil then
local realm = Skills.getRealmFromName(realmName)
    local rowText = '|-'
if realm == nil then
    rowText = rowText..'\r\n|data-sort-value="'..item.name..'"|'..Icons.Icon({item.name, type='mark', notext=true, size='50'})
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
    rowText = rowText..'||'..Icons.Icon({item.name, 'Mark of the ' .. item.name, type='mark', noicon=true})
    rowText = rowText..'||style="text-align:right"|'..Fam.level
    local discoveredArray = {}
    for j, SkillID in Shared.skpairs(Fam.skills) do
      table.insert(discoveredArray, Icons.Icon({Constants.getSkillName(SkillID), type='skill'}))
    end
    rowText = rowText..'||'..table.concat(discoveredArray, '<br/>')
    table.insert(rowArray, rowText)
end
end
  end
local skillID = 'Summoning'


  result = result..'\r\n'..table.concat(rowArray, '\r\n')
local html = mw.html.create('table')
:addClass('wikitable sortable stickyHeader col-1-img col-5-img')
html:tag('tr'):addClass('headerRow-0')
:tag('th'):wikitext('Mark')
  :attr('colspan', 2)
:tag('th'):wikitext('DLC')  
:tag('th'):wikitext(Icons._SkillRealmIcon(skillID, realm.id) .. '<br>Level')
:tag('th'):wikitext('Tier')
:tag('th'):wikitext('Discovered in')


  result = result..'\r\n|}'
local Familiars = GameData.getEntities(SkillData.Summoning.recipes,
  return result
function(obj)
end
return Skills.getRecipeRealm(obj) == realm.id
end
)
table.sort(Familiars, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)


function p.getTabletTable(frame)
local rowArray = {}
  local result = ''
for i, Fam in ipairs(Familiars) do
  result = result..'{| class="wikitable sortable stickyHeader"'
local level = Skills.getRecipeLevel(skillID, Fam)
  result = result..'\r\n|- class="headerRow-0"'
local reqText = Skills.getRecipeRequirementText(SkillData.Summoning.name, Fam)
  result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Summoning', type='skill', notext=true})..' Level'
local item = Items.getItemByID(Fam.productID)
  result = result..'!!Tier!!Effect!!' .. Icons.Icon({'Melee', notext=true, nolink=true}) .. ' Max Hit!!Description!!Shard Cost!!Secondary!!Creation XP'
if item ~= nil then
 
local row = html:tag('tr')
  local Familiars = {}
row:tag('td'):wikitext(Icons.Icon({item.name, type='mark', notext=true}))
  for i, recipe in ipairs(SkillData.Summoning.Marks) do
:attr('data-sort-value', item.name)
  table.insert(Familiars, recipe)
row:tag('td'):wikitext(Icons.Icon({item.name, 'Mark of the ' .. item.name, type='mark', noicon=true}))
  end
row:tag('td'):wikitext(Icons.getDLCColumnIcon(Fam.id))
  table.sort(Familiars, function(a, b)
:attr('data-sort-value', Icons.getExpansionID(Fam.id))
                          if a.level == b.level then
:css('text-align', 'center')
                            return a.masteryID < b.masteryID
row:tag('td'):wikitext(level)
                          else
:css('text-align', 'center')
                            return a.level < b.level
row:tag('td'):wikitext(Fam.tier)
                          end
:css('text-align', 'center')
                        end)
 
  local rowArray = {}
  for i, Fam in ipairs(Familiars) do
local item = Items.getItemByID(Fam.itemID)
if item ~= nil then
    local maxHit, maxHitText = Items._getItemStat(item, 'summoningMaxhit'), ''
    if maxHit ~= nil then
      maxHit = maxHit * 10
      maxHitText = 'style="text-align:right"; data-sort-value="' .. maxHit .. '"|' .. Shared.formatnum(maxHit)
    end
    local rowText = '|-'
    rowText = rowText..'\r\n|data-sort-value="'..item.name..'"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
    rowText = rowText..'||'..Icons.Icon({item.name, type='item', noicon=true})
    rowText = rowText..'||style="text-align:right"|'..Fam.level
    rowText = rowText..'||style="text-align:right"|'..Fam.tier
    rowText = rowText..'||'..item.description..'||'..maxHitText..'||'..Fam.description


-- Create item requirements text
local discoveredArray = {}
local ShardCostArray, OtherCostArray = {}, {}
for j, SkillID in ipairs(Fam.skillIDs) do
-- Shards
table.insert(discoveredArray, Icons.Icon({Constants.getSkillName(SkillID), type='skill'}))
for j, cost in ipairs(Fam.itemCosts) do
local shard = Items.getItemByID(cost.id)
if shard ~= nil then
table.insert(ShardCostArray, Icons.Icon({shard.name, type='item', notext=true, qty=cost.qty}))
end
end
row:tag('td'):wikitext(table.concat(discoveredArray, '<br/>'))
end
end
rowText = rowText..'||style="text-align:right"|'..table.concat(ShardCostArray, ', ')..'&nbsp;'
end
return tostring(html)
end


-- Other costs
function p.getTabletTable(frame)
local recipeGPCost = SkillData.Summoning.RecipeGPCost
local args = frame.args ~= nil and frame.args or frame
if Fam.gpCost > 0 then
local realmName = args.realm
table.insert(OtherCostArray, Icons.GP(Fam.gpCost))
local category = args.category
end
local realm = Skills.getRealmFromName(realmName)
if Fam.scCost > 0 then
if realm == nil then
table.insert(OtherCostArray, Icons.SC(Fam.scCost))
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
end
for j, nonShardID in ipairs(Fam.nonShardItemCosts) do
local skillID = 'Summoning'
local nonShard = Items.getItemByID(nonShardID)
if nonShard ~= nil then
local html = mw.html.create('table')
local itemValue = math.max(nonShardItem.sellsFor, 20)
:addClass('wikitable sortable stickyHeader col-1-img col-3-img')
local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
table.insert(OtherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty}))
local header = html:tag('tr'):addClass('headerRow-0')
header:tag('th'):wikitext('Name')
            :attr('colspan', 2)
header:tag('th'):wikitext('DLC')
header:tag('th'):wikitext(Icons._SkillRealmIcon(skillID, realm.id) .. '<br>Level')
header:tag('th'):wikitext('Tier')
header:tag('th'):wikitext('Creation<br>XP')
header:tag('th'):wikitext('Shards')
header:tag('th'):wikitext('Cost of shards')
if category == 'Combat' then
header:tag('th'):wikitext(Icons.Icon({'Melee', notext=true, nolink=true}) .. '<br>Max Hit')
end
local function getShardCosts(familiar)
local shardCosts = {}
for _, shard in ipairs(familiar.itemCosts) do
local shopItem = Shop.getPurchaseByID(shard.id)
for _, shardCost in ipairs(shopItem.cost.currencies) do
local addCost = shardCost.cost * shard.quantity
if shardCosts[shardCost.currency] ~= nil then
shardCosts[shardCost.currency] = shardCosts[shardCost.currency] + addCost
else
shardCosts[shardCost.currency] = addCost
end
end
end
end
end
rowText = rowText..'||style="text-align:right"|'..table.concat(OtherCostArray, "<br/>'''OR''' ")
    return shardCosts
end


rowText = rowText..'||style="text-align:right"|'..Fam.baseXP
local function isCombatFamiliar(fam)
 
local item = Items.getItemByID(fam.productID)
    table.insert(rowArray, rowText)
local maxHit = Items._getItemStat(item, 'summoningMaxhitAbyssal') or Items._getItemStat(item, 'summoningMaxhit')
return maxHit ~= nil
end
end
  end


  result = result..'\r\n'..table.concat(rowArray, '\r\n')
local Familiars = GameData.getEntities(SkillData.Summoning.recipes,
function(recipe)
    return Skills.getRecipeRealm(recipe) == realm.id and
          ((category == 'Combat' and isCombatFamiliar(recipe)) or
          (category ~= 'Combat' and not isCombatFamiliar(recipe)))
end
)
table.sort(Familiars, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)


  result = result..'\r\n|}'
local rowArray = {}
  return result
for i, Fam in ipairs(Familiars) do
end
local level, isAbyssal = Skills.getRecipeLevelRealm(skillID, Fam)
local baseXP = Fam.baseAbyssalExperience or Fam.baseExperience
local reqText = Skills.getRecipeRequirementText(SkillData.Summoning.name, Fam)
local item = Items.getItemByID(Fam.productID)
if item ~= nil then
local row = html:tag('tr')
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', notext=true}))
:attr('data-sort-value', item.name)
row:tag('td'):wikitext('[[' .. item.name .. ']]')
row:tag('td'):wikitext(Icons.getDLCColumnIcon(Fam.id))
:attr('data-sort-value', Icons.getExpansionID(Fam.id))
row:tag('td'):wikitext(level)
:css('text-align', 'center')
row:tag('td'):wikitext(Fam.tier)
:css('text-align', 'center')
row:tag('td'):wikitext(Num.formatnum(baseXP))
:css('text-align', 'right')
:attr('data-sort-value', baseXP)
local shardCell = row:tag('td')
local shardCostCell = row:tag('td')
if category == 'Combat' then
local maxHit = (Items._getItemStat(item, 'summoningMaxhitAbyssal') or Items._getItemStat(item, 'summoningMaxhit')) * 10
row:tag('td'):wikitext(Num.formatnum(maxHit))
:css('text-align', 'right')
:attr('data-sort-value', maxHit)
end


function p.getSynergyTable(frame)
-- Shards required
  local result = ''
for _, cost in ipairs(Fam.itemCosts) do
  result = result..'{| class="wikitable sortable stickyHeader"'
local shard = Items.getItemByID(cost.id)
  result = result..'\r\n|- class="headerRow-0"'
if shard ~= nil then
  result = result..'\r\n!colspan="2"|Familiar 1!!colspan="2"|Familiar 2!!Effect!!Requirements'
local sub = mw.html.create('sub')
:wikitext(cost.quantity .. 'x'):addClass('item-qty'):done()
shardCell:node(sub)
shardCell:wikitext(Icons.Icon({shard.name, type='item', notext=true}))
end
end
local costTbl = {}
for costType, cost in pairs(getShardCosts(Fam)) do
table.insert(costTbl, Icons._Currency(costType, cost))
end
shardCostCell:wikitext(table.concat(costTbl, '<br>'))
:css('text-align', 'right')
end
end


  local synergyList = {}
return tostring(html)
  for i, syn in ipairs(SkillData.Summoning.Synergies) do
  table.insert(synergyList, syn)
  end
  table.sort(synergyList, function (a, b) return (a.summons[1] == b.summons[1] and a.summons[2] < b.summons[2]) or a.summons[1] < b.summons[1] end)
 
  local marksByID, famNames = {}, {}
  for i, recipe in ipairs(SkillData.Summoning.Marks) do
  marksByID[recipe.masteryID] = recipe
  local item = Items.getItemByID(recipe.itemID)
  if item ~= nil then
  famNames[recipe.masteryID] = item.name
  end
  end
 
  local rowArray = {}
  for i, syn in ipairs(synergyList) do
local Fam1 = marksByID[syn.summons[1]]
local Fam2 = marksByID[syn.summons[2]]
if Fam1 ~= nil and Fam2 ~= nil then
      local FamName1 = famNames[Fam1.masteryID] or 'Unknown'
  local FamName2 = famNames[Fam2.masteryID] or 'Unknown'
      local rowText = '|-'
      rowText = rowText..'\r\n|data-sort-value="'..FamName1..'"|'..Icons.Icon({FamName1, type='item', notext=true, size='30'})
      rowText = rowText..'||'..Icons.Icon({FamName1, type='item', noicon=true})
      rowText = rowText..'||data-sort-value="'..FamName2..'"|'..Icons.Icon({FamName2, type='item', notext=true, size='30'})
      rowText = rowText..'||'..Icons.Icon({FamName2, type='item', noicon=true})
      rowText = rowText..'||'..syn.description
 
      local reqArray = {}
      local reqLvl = math.max(Fam1.level, Fam2.level)
      table.insert(reqArray, Icons._SkillReq('Summoning', reqLvl))
      table.insert(reqArray, FamName1..' Mark Level '..(Fam2.tier + 1))
      table.insert(reqArray, FamName2..' Mark Level '..(Fam1.tier + 1))
 
      rowText = rowText..'||data-sort-value="'..reqLvl..'"|'..table.concat(reqArray, '<br/>')
 
      table.insert(rowArray, rowText)
    end
  end
 
  result = result..'\r\n'..table.concat(rowArray, '\r\n')
 
  result = result..'\r\n|}'
  return result
end
end


function p._getFamiliarSynergyTable(familiarID)
function p._getSynergyTable(familiarIDs)
local skillID = 'Summoning'
local result = ''
local result = ''
result = result..'{| class="wikitable sortable stickyHeader"'
result = result..'{| class="wikitable sortable stickyHeader col-1-img col-3-img"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Familiar 1!!colspan="2"|Familiar 2!!Effect!!Requirements'
result = result..'\r\n!colspan="2"|Familiar 1!!colspan="2"|Familiar 2!!Effect!!Requirements'


local synergyList = {}
local recipesByID, famNames = {}, {}
for i, syn in ipairs(SkillData.Summoning.Synergies) do
for i, recipe in ipairs(SkillData.Summoning.recipes) do
if Shared.contains(syn.summons, familiarID) then
recipesByID[recipe.id] = recipe
table.insert(synergyList, syn)
local item = Items.getItemByID(recipe.productID)
if item ~= nil then
famNames[recipe.id] = item.name
end
end
end
end
    table.sort(synergyList, function (a, b) return (a.summons[1] == b.summons[1] and a.summons[2] < b.summons[2]) or a.summons[1] < b.summons[1] end)


local marksByID, famNames = {}, {}
local synergyList = GameData.getEntities(SkillData.Summoning.synergies,
for i, recipe in ipairs(SkillData.Summoning.Marks) do
function(synergy)
marksByID[recipe.masteryID] = recipe
for i, summonID in ipairs(synergy.summonIDs) do
local item = Items.getItemByID(recipe.itemID)
if Shared.contains(familiarIDs, summonID) then
if item ~= nil then
return true
  famNames[recipe.masteryID] = item.name
end
end
return false
end)
table.sort(synergyList,
function (a, b)
local recA1, recB1 = recipesByID[a.summonIDs[1]], recipesByID[b.summonIDs[1]]
 
if ((recA1.abyssalLevel or 0) == (recB1.abyssalLevel or 0)) and (recA1.level == recB1.level) then
return (
(a.summonIDs[1] == b.summonIDs[1] and a.summonIDs[2] < b.summonIDs[2])
or a.summonIDs[1] < b.summonIDs[1]
)
else
return Skills.standardRecipeSort(skillID, recA1, recB1)
end
end
end
end
)


local rowArray = {}
local rowArray = {}
for i, syn in ipairs(synergyList) do
for i, syn in ipairs(synergyList) do
local Fam1 = marksByID[syn.summons[1]]
local Fam1 = recipesByID[syn.summonIDs[1]]
local Fam2 = marksByID[syn.summons[2]]
local Fam2 = recipesByID[syn.summonIDs[2]]
if Fam1 ~= nil and Fam2 ~= nil then
if Fam1 ~= nil and Fam2 ~= nil then
local FamName1 = famNames[Fam1.masteryID] or 'Unknown'
local FamName1 = famNames[Fam1.id] or 'Unknown'
local FamName2 = famNames[Fam2.masteryID] or 'Unknown'
local FamName2 = famNames[Fam2.id] or 'Unknown'
local synDesc = syn.customDescription
if synDesc == nil then
-- Generate description from modifiers
synDesc = getSummonModifierText(syn, 10)
end
local rowText = '|-'
local rowText = '|-'
rowText = rowText..'\r\n|data-sort-value="'..FamName1..'"|'..Icons.Icon({FamName1, type='item', notext=true, size='30'})
rowText = rowText..'\r\n|data-sort-value="'..FamName1..'"|'..Icons.Icon({FamName1, type='item', notext=true})
rowText = rowText..'||'..Icons.Icon({FamName1, type='item', noicon=true})
rowText = rowText..'||' .. Icons.getExpansionIcon(Fam1.id) .. Icons.Icon({FamName1, type='item', noicon=true})
rowText = rowText..'||data-sort-value="'..FamName2..'"|'..Icons.Icon({FamName2, type='item', notext=true, size='30'})
rowText = rowText..'||data-sort-value="'..FamName2..'"|'..Icons.Icon({FamName2, type='item', notext=true})
rowText = rowText..'||'..Icons.Icon({FamName2, type='item', noicon=true})
rowText = rowText..'||' .. Icons.getExpansionIcon(Fam2.id) .. Icons.Icon({FamName2, type='item', noicon=true})
rowText = rowText..'||'..syn.description
rowText = rowText..'||'..synDesc


local reqArray = {}
local reqArray = {}
local reqLvl = math.max(Fam1.level, Fam2.level)
local reqFam = (Skills.getRecipeLevel(skillID, Fam1) > Skills.getRecipeLevel(skillID, Fam2) and Fam1) or Fam2
table.insert(reqArray, Icons._SkillReq('Summoning', reqLvl))
local reqLvl = Skills.getRecipeLevel(skillID, reqFam)
table.insert(reqArray, Skills.getRecipeRequirementText(skillID, reqFam))
table.insert(reqArray, FamName1..' Mark Level '..(Fam2.tier + 1))
table.insert(reqArray, FamName1..' Mark Level '..(Fam2.tier + 1))
table.insert(reqArray, FamName2..' Mark Level '..(Fam1.tier + 1))
table.insert(reqArray, FamName2..' Mark Level '..(Fam1.tier + 1))
rowText = rowText..'||data-sort-value="'..reqLvl..'"|'..table.concat(reqArray, '<br/>')
rowText = rowText..'||data-sort-value="'..reqLvl..'"|'..table.concat(reqArray, '<br/>')


Line 233: Line 276:


result = result..'\r\n'..table.concat(rowArray, '\r\n')
result = result..'\r\n'..table.concat(rowArray, '\r\n')
result = result..'\r\n|}'
result = result..'\r\n|}'
return result
return result
end
function p.getSynergyTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local familiarIDs = {}
for i, recipe in ipairs(SkillData.Summoning.recipes) do
if Skills.getRecipeRealm(recipe) == realm.id then
table.insert(familiarIDs, recipe.id)
end
end
return p._getSynergyTable(familiarIDs)
end
end


Line 242: Line 304:
local familiarItem = Items.getItem(famName)
local familiarItem = Items.getItem(famName)
if familiarItem == nil then
if familiarItem == nil then
return 'ERROR: Not a valid familiar[[Category:Pages with script errors]]'
return Shared.printError('Not a valid familiar')
else
else
for i, recipe in ipairs(SkillData.Summoning.Marks) do
for i, recipe in ipairs(SkillData.Summoning.recipes) do
if recipe.itemID == familiarItem.id then
if recipe.productID == familiarItem.id then
familiarID = recipe.masteryID
familiarID = recipe.id
break
break
end
end
end
end
if familiarID == nil then
if familiarID == nil then
return 'ERROR: Not a valid familiar[[Category:Pages with script errors]]'
return Shared.printError('Not a valid familiar')
else
else
return p._getFamiliarSynergyTable(familiarID)
return p._getSynergyTable({ familiarID })
end
end
end
end
Line 263: Line 325:


-- Familiars
-- Familiars
for i, recipe in ipairs(SkillData.Summoning.Marks) do
for i, recipe in ipairs(SkillData.Summoning.recipes) do
local item = Items.getItemByID(recipe.itemID)
local item = Items.getItemByID(recipe.productID)
if item ~= nil then
if item ~= nil then
famNames[recipe.masteryID] = item.name
famNames[recipe.id] = item.name
if item.modifiers ~= nil then
if item.modifiers ~= nil and not Shared.tableIsEmpty(item.modifiers) then
local famSkills = Constants.getModifierSkills(item.modifiers)
local famSkills = Modifiers.getModifierSkills(item.modifiers)
if Shared.contains(famSkills, skill) then
if Shared.contains(famSkills, skill) then
table.insert(rowArray, {Fam1 = item.name, Fam2 = nil, Descrip = Constants.getModifiersText(item.modifiers, false)})
table.insert(rowArray, {Fam1 = item.name, FamID1 = item.id, Fam2 = nil, FamID2 = nil, Descrip = getSummonModifierText(item)})
end
end
end
end
Line 277: Line 339:


-- Synergies
-- Synergies
for i, syn in ipairs(SkillData.Summoning.Synergies) do
for i, syn in ipairs(SkillData.Summoning.synergies) do
local synSkills = Constants.getModifierSkills(syn.modifiers)
local synSkills = Modifiers.getModifierSkills(syn.modifiers)
if Shared.contains(synSkills, skill) then
if Shared.contains(synSkills, skill) then
local FamName1 = famNames[syn.summons[1]] or 'Unknown'
local FamName1 = famNames[syn.summonIDs[1]] or 'Unknown'
local FamName2 = famNames[syn.summons[2]] or 'Unknown'
local FamName2 = famNames[syn.summonIDs[2]] or 'Unknown'
table.insert(rowArray, {Fam1 = FamName1, Fam2 = FamName2, Descrip = syn.description})
local synDesc = syn.customDescription
if synDesc == nil then
-- Generate description from modifiers
synDesc = getSummonModifierText(syn)
end
table.insert(rowArray, {Fam1 = FamName1, FamID1 = syn.summonIDs[1], Fam2 = FamName2, FamID2 = syn.summonIDs[2], Descrip = synDesc})
end
end
end
end


if Shared.tableCount(rowArray) == 0 then
if Shared.tableIsEmpty(rowArray) then
return ''
return ''
end
end


local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
:addClass('wikitable sortable stickyHeader col-1-img col-3-img')
  table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!colspan="2"|Familiar 1!!colspan="2"|Familiar 2!!Effect')
html:tag('tr'):addClass('headerRow-0')
:tag('th'):wikitext('Familiar 1')
  :attr('colspan', 2)
:tag('th'):wikitext('Familiar 2')
  :attr('colspan', 2)
:tag('th'):wikitext('DLC')
:tag('th'):wikitext('Effect')


for i, rowItem in Shared.skpairs(rowArray) do
for i, rowItem in ipairs(rowArray) do
table.insert(resultPart, '\r\n|-')
local DLCIcon = Icons.getExpansionIcon(rowItem.FamID1)
    table.insert(resultPart, '\r\n|data-sort-value="'..rowItem.Fam1..'"|'..Icons.Icon({rowItem.Fam1, type='item', notext=true, size='30'}))
local row = html:tag('tr')
table.insert(resultPart, '||'..Icons.Icon({rowItem.Fam1, type='item', noicon=true}))
row:tag('td'):wikitext(Icons.Icon({rowItem.Fam1, type='item', notext=true}))
:attr('data-sort-value', rowItem.Fam1)
row:tag('td'):wikitext(Icons.Icon({rowItem.Fam1, type='item', noicon=true}))
if rowItem.Fam2 ~= nil then
if rowItem.Fam2 ~= nil then
table.insert(resultPart, '||data-sort-value="'..rowItem.Fam2..'"|'..Icons.Icon({rowItem.Fam2, type='item', notext=true, size='30'}))
-- If Fam1 has no DLC, try setting it for Fam2
table.insert(resultPart, '||'..Icons.Icon({rowItem.Fam2, type='item', noicon=true}))
if DLCIcon == nil or DLCIcon == '' then
DLCIcon = Icons.getExpansionIcon(rowItem.FamID2)
end
row:tag('td'):wikitext(Icons.Icon({rowItem.Fam2, type='item', notext=true}))
:attr('data-sort-value', rowItem.Fam2)
row:tag('td'):wikitext(Icons.Icon({rowItem.Fam2, type='item', noicon=true}))
else
else
table.insert(resultPart, '|| || ')
row:tag('td'):tag('td')
end
 
-- If both familiars have no DLC, default to melvor icon.
if DLCIcon == nil or DLCIcon == '' then
DLCIcon = Icons.Melvor()
end
end
table.insert(resultPart, '||'..(rowItem.Descrip or ' '))
-- As of 06/07/2024, no synergies exist that use two DLCs
row:tag('td'):wikitext(DLCIcon)
:css('text-align', 'center')
 
row:tag('td'):wikitext(rowItem.Descrip or ' ')
end
end
return tostring(html)


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



Latest revision as of 23:17, 7 October 2024

Documentation for this module may be created at Module:Skills/Summoning/doc

local p = {}

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Skills = require('Module:Skills')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Shop = require('Module:Shop')
local Num = require('Module:Number')


-- Gets a familiar recipe by ID, e.g. melvorF:GolbinThief
function p.getFamiliarRecipeByID(id)
	return GameData.getEntityByID(SkillData.Summoning.recipes, id)
end

function p.getSynergies(checkFunc)
	local result = {}
	for i, synergy in pairs(SkillData.Summoning.synergies) do
		if checkFunc(synergy) then
			table.insert(result, synergy)
		end
	end
	return result
end

local function getSummonModifierText(summonItem, maxVisible)
	local playerMods = Modifiers.getModifiersText(summonItem.modifiers, false, false, maxVisible) or ''
	local enemyMods = Modifiers.getModifiersText(summonItem.enemyModifiers, false, false, maxVisible, function(text) return 'Gives the enemy: ' .. text	end) or ''
	return playerMods .. (playerMods ~= '' and '<br>' or '') .. enemyMods
end

function p.getMarkTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local realmName = args.realm
	local realm = Skills.getRealmFromName(realmName)
	if realm == nil then
		return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
	end
	local skillID = 'Summoning'

	local html = mw.html.create('table')
		:addClass('wikitable sortable stickyHeader col-1-img col-5-img')
		
	html:tag('tr'):addClass('headerRow-0')
			:tag('th'):wikitext('Mark')
					  :attr('colspan', 2)
			:tag('th'):wikitext('DLC')					  
			:tag('th'):wikitext(Icons._SkillRealmIcon(skillID, realm.id) .. '<br>Level')
			:tag('th'):wikitext('Tier')
			:tag('th'):wikitext('Discovered in')

	local Familiars = GameData.getEntities(SkillData.Summoning.recipes,
		function(obj)
			return Skills.getRecipeRealm(obj) == realm.id
		end
	)
	table.sort(Familiars, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)

	local rowArray = {}
	for i, Fam in ipairs(Familiars) do
		local level = Skills.getRecipeLevel(skillID, Fam)
		local reqText = Skills.getRecipeRequirementText(SkillData.Summoning.name, Fam)
		local item = Items.getItemByID(Fam.productID)
		if item ~= nil then
			local row = html:tag('tr')
			row:tag('td'):wikitext(Icons.Icon({item.name, type='mark', notext=true}))
						 :attr('data-sort-value', item.name)
			row:tag('td'):wikitext(Icons.Icon({item.name, 'Mark of the ' .. item.name, type='mark', noicon=true}))
			row:tag('td'):wikitext(Icons.getDLCColumnIcon(Fam.id))
						 :attr('data-sort-value', Icons.getExpansionID(Fam.id))		
						 :css('text-align', 'center')
			row:tag('td'):wikitext(level)
						 :css('text-align', 'center')	
			row:tag('td'):wikitext(Fam.tier)
						 :css('text-align', 'center')

			local discoveredArray = {}
			for j, SkillID in ipairs(Fam.skillIDs) do
				table.insert(discoveredArray, Icons.Icon({Constants.getSkillName(SkillID), type='skill'}))
			end
			
			row:tag('td'):wikitext(table.concat(discoveredArray, '<br/>'))
		end
	end
	
	return tostring(html)
end

function p.getTabletTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local realmName = args.realm
	local category = args.category
	local realm = Skills.getRealmFromName(realmName)
	if realm == nil then
		return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
	end
	local skillID = 'Summoning'
	
	local html = mw.html.create('table')
		:addClass('wikitable sortable stickyHeader col-1-img col-3-img')
	
	local header = html:tag('tr'):addClass('headerRow-0')
	header:tag('th'):wikitext('Name')
		            :attr('colspan', 2)
	header:tag('th'):wikitext('DLC')
	header:tag('th'):wikitext(Icons._SkillRealmIcon(skillID, realm.id) .. '<br>Level')
	header:tag('th'):wikitext('Tier')
	header:tag('th'):wikitext('Creation<br>XP')
	header:tag('th'):wikitext('Shards')
	header:tag('th'):wikitext('Cost of shards')
	
	if category == 'Combat' then
		header:tag('th'):wikitext(Icons.Icon({'Melee', notext=true, nolink=true}) .. '<br>Max Hit')
	end
	
	local function getShardCosts(familiar)
		local shardCosts = {}
		for _, shard in ipairs(familiar.itemCosts) do
			local shopItem = Shop.getPurchaseByID(shard.id)
			for _, shardCost in ipairs(shopItem.cost.currencies) do
				local addCost = shardCost.cost * shard.quantity
				if shardCosts[shardCost.currency] ~= nil then
					shardCosts[shardCost.currency] = shardCosts[shardCost.currency] + addCost
				else
					shardCosts[shardCost.currency] = addCost
				end
			end
		end
    	return shardCosts
	end

	local function isCombatFamiliar(fam)
		local item = Items.getItemByID(fam.productID)
		local maxHit = Items._getItemStat(item, 'summoningMaxhitAbyssal') or Items._getItemStat(item, 'summoningMaxhit')
		return maxHit ~= nil
	end

	local Familiars = GameData.getEntities(SkillData.Summoning.recipes,
		function(recipe)
    		return Skills.getRecipeRealm(recipe) == realm.id and 
           ((category == 'Combat' and isCombatFamiliar(recipe)) or
           (category ~= 'Combat' and not isCombatFamiliar(recipe)))
		end
	)
	table.sort(Familiars, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)

	local rowArray = {}
	for i, Fam in ipairs(Familiars) do
		local level, isAbyssal = Skills.getRecipeLevelRealm(skillID, Fam)
		local baseXP = Fam.baseAbyssalExperience or Fam.baseExperience
		local reqText = Skills.getRecipeRequirementText(SkillData.Summoning.name, Fam)
		local item = Items.getItemByID(Fam.productID)
		if item ~= nil then
			local row = html:tag('tr')
			row:tag('td'):wikitext(Icons.Icon({item.name, type='item', notext=true}))
						 :attr('data-sort-value', item.name)
			row:tag('td'):wikitext('[[' .. item.name .. ']]')
			row:tag('td'):wikitext(Icons.getDLCColumnIcon(Fam.id))
						 :attr('data-sort-value', Icons.getExpansionID(Fam.id))
			row:tag('td'):wikitext(level)
						 :css('text-align', 'center')
			row:tag('td'):wikitext(Fam.tier)
						 :css('text-align', 'center')
			row:tag('td'):wikitext(Num.formatnum(baseXP))
						 :css('text-align', 'right')
						 :attr('data-sort-value', baseXP)
			local shardCell = row:tag('td')
			local shardCostCell = row:tag('td')
			
			if category == 'Combat' then
				local maxHit = (Items._getItemStat(item, 'summoningMaxhitAbyssal') or Items._getItemStat(item, 'summoningMaxhit')) * 10
				row:tag('td'):wikitext(Num.formatnum(maxHit))
							 :css('text-align', 'right')
							 :attr('data-sort-value', maxHit)
			end

			-- Shards required
			for _, cost in ipairs(Fam.itemCosts) do
				local shard = Items.getItemByID(cost.id)
				if shard ~= nil then
					local sub = mw.html.create('sub')
						:wikitext(cost.quantity .. 'x'):addClass('item-qty'):done()
					shardCell:node(sub)
					shardCell:wikitext(Icons.Icon({shard.name, type='item', notext=true}))
				end
			end
			
			local costTbl = {}
			for costType, cost in pairs(getShardCosts(Fam)) do
				table.insert(costTbl, Icons._Currency(costType, cost))
			end
			shardCostCell:wikitext(table.concat(costTbl, '<br>'))
						 :css('text-align', 'right')
		end
	end

	return tostring(html)
end

function p._getSynergyTable(familiarIDs)
	local skillID = 'Summoning'
	local result = ''
	result = result..'{| class="wikitable sortable stickyHeader col-1-img col-3-img"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Familiar 1!!colspan="2"|Familiar 2!!Effect!!Requirements'

	local recipesByID, famNames = {}, {}
	for i, recipe in ipairs(SkillData.Summoning.recipes) do
		recipesByID[recipe.id] = recipe
		local item = Items.getItemByID(recipe.productID)
		if item ~= nil then
			famNames[recipe.id] = item.name
		end
	end

	local synergyList = GameData.getEntities(SkillData.Summoning.synergies,
		function(synergy)
			for i, summonID in ipairs(synergy.summonIDs) do
				if Shared.contains(familiarIDs, summonID) then
					return true
				end
			end
			return false
		end)
	table.sort(synergyList,
		function (a, b)
			local recA1, recB1 = recipesByID[a.summonIDs[1]], recipesByID[b.summonIDs[1]]

			if ((recA1.abyssalLevel or 0) == (recB1.abyssalLevel or 0)) and (recA1.level == recB1.level) then
				return (
					(a.summonIDs[1] == b.summonIDs[1] and a.summonIDs[2] < b.summonIDs[2])
					or a.summonIDs[1] < b.summonIDs[1]
				 )
			else
				return Skills.standardRecipeSort(skillID, recA1, recB1)
			end
		end
	)

	local rowArray = {}
	for i, syn in ipairs(synergyList) do
		local Fam1 = recipesByID[syn.summonIDs[1]]
		local Fam2 = recipesByID[syn.summonIDs[2]]
		if Fam1 ~= nil and Fam2 ~= nil then
			local FamName1 = famNames[Fam1.id] or 'Unknown'
			local FamName2 = famNames[Fam2.id] or 'Unknown'
			local synDesc = syn.customDescription
			if synDesc == nil then
				-- Generate description from modifiers
				synDesc = getSummonModifierText(syn, 10)
			end
			local rowText = '|-'
			rowText = rowText..'\r\n|data-sort-value="'..FamName1..'"|'..Icons.Icon({FamName1, type='item', notext=true})
			rowText = rowText..'||' .. Icons.getExpansionIcon(Fam1.id) .. Icons.Icon({FamName1, type='item', noicon=true})
			rowText = rowText..'||data-sort-value="'..FamName2..'"|'..Icons.Icon({FamName2, type='item', notext=true})
			rowText = rowText..'||' .. Icons.getExpansionIcon(Fam2.id) .. Icons.Icon({FamName2, type='item', noicon=true})
			rowText = rowText..'||'..synDesc

			local reqArray = {}
			local reqFam = (Skills.getRecipeLevel(skillID, Fam1) > Skills.getRecipeLevel(skillID, Fam2) and Fam1) or Fam2
			local reqLvl = Skills.getRecipeLevel(skillID, reqFam)
			table.insert(reqArray, Skills.getRecipeRequirementText(skillID, reqFam))
			table.insert(reqArray, FamName1..' Mark Level '..(Fam2.tier + 1))
			table.insert(reqArray, FamName2..' Mark Level '..(Fam1.tier + 1))

			rowText = rowText..'||data-sort-value="'..reqLvl..'"|'..table.concat(reqArray, '<br/>')

			table.insert(rowArray, rowText)
		end
	end

	result = result..'\r\n'..table.concat(rowArray, '\r\n')

	result = result..'\r\n|}'
	return result
end

function p.getSynergyTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local realmName = args.realm
	local realm = Skills.getRealmFromName(realmName)
	if realm == nil then
		return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
	end

	local familiarIDs = {}
	for i, recipe in ipairs(SkillData.Summoning.recipes) do
		if Skills.getRecipeRealm(recipe) == realm.id then
			table.insert(familiarIDs, recipe.id)
		end
	end

	return p._getSynergyTable(familiarIDs)
end

function p.getFamiliarSynergyTable(frame)
	local famName = frame.args ~= nil and frame.args[1] or frame
	local familiarID = nil
	local familiarItem = Items.getItem(famName)
	if familiarItem == nil then
		return Shared.printError('Not a valid familiar')
	else
		for i, recipe in ipairs(SkillData.Summoning.recipes) do
			if recipe.productID == familiarItem.id then
				familiarID = recipe.id
				break
			end
		end
		if familiarID == nil then
			return Shared.printError('Not a valid familiar')
		else
			return p._getSynergyTable({ familiarID })
		end
	end
end

function p._getSkillSummoningBonusTable(skill)
	local rowArray = {}
	local famNames = {}

	-- Familiars
	for i, recipe in ipairs(SkillData.Summoning.recipes) do
		local item = Items.getItemByID(recipe.productID)
		if item ~= nil then
			famNames[recipe.id] = item.name
			if item.modifiers ~= nil and not Shared.tableIsEmpty(item.modifiers) then
				local famSkills = Modifiers.getModifierSkills(item.modifiers)
				if Shared.contains(famSkills, skill) then
					table.insert(rowArray, {Fam1 = item.name, FamID1 = item.id, Fam2 = nil, FamID2 = nil, Descrip = getSummonModifierText(item)})
				end
			end
		end
	end

	-- Synergies
	for i, syn in ipairs(SkillData.Summoning.synergies) do
		local synSkills = Modifiers.getModifierSkills(syn.modifiers)
		if Shared.contains(synSkills, skill) then
			local FamName1 = famNames[syn.summonIDs[1]] or 'Unknown'
			local FamName2 = famNames[syn.summonIDs[2]] or 'Unknown'
			local synDesc = syn.customDescription
			if synDesc == nil then
				-- Generate description from modifiers
				synDesc = getSummonModifierText(syn)
			end
			table.insert(rowArray, {Fam1 = FamName1, FamID1 = syn.summonIDs[1], Fam2 = FamName2, FamID2 = syn.summonIDs[2], Descrip = synDesc})
		end
	end

	if Shared.tableIsEmpty(rowArray) then
		return ''
	end

	local html = mw.html.create('table')
		:addClass('wikitable sortable stickyHeader col-1-img col-3-img')
		
	html:tag('tr'):addClass('headerRow-0')
			:tag('th'):wikitext('Familiar 1')
					  :attr('colspan', 2)
			:tag('th'):wikitext('Familiar 2')
					  :attr('colspan', 2)
			:tag('th'):wikitext('DLC')
			:tag('th'):wikitext('Effect')
	
	

	for i, rowItem in ipairs(rowArray) do
		local DLCIcon = Icons.getExpansionIcon(rowItem.FamID1)
		local row = html:tag('tr')
		row:tag('td'):wikitext(Icons.Icon({rowItem.Fam1, type='item', notext=true}))
					 :attr('data-sort-value', rowItem.Fam1)
		row:tag('td'):wikitext(Icons.Icon({rowItem.Fam1, type='item', noicon=true}))
		
		if rowItem.Fam2 ~= nil then
			-- If Fam1 has no DLC, try setting it for Fam2
			if DLCIcon == nil or DLCIcon == '' then
				DLCIcon = Icons.getExpansionIcon(rowItem.FamID2)
			end
			row:tag('td'):wikitext(Icons.Icon({rowItem.Fam2, type='item', notext=true}))
						 :attr('data-sort-value', rowItem.Fam2)
			row:tag('td'):wikitext(Icons.Icon({rowItem.Fam2, type='item', noicon=true}))
		else
			row:tag('td'):tag('td')
		end

		-- If both familiars have no DLC, default to melvor icon.
		if DLCIcon == nil or DLCIcon == '' then
			DLCIcon = Icons.Melvor()
		end
		-- As of 06/07/2024, no synergies exist that use two DLCs
		row:tag('td'):wikitext(DLCIcon)
					 :css('text-align', 'center')

		row:tag('td'):wikitext(rowItem.Descrip or ' ')
	end
	
	return tostring(html)

end

function p.getSkillSummoningBonusTable(frame)
	local skillName = frame.args ~= nil and frame.args[1] or frame
	return p._getSkillSummoningBonusTable(skillName)
end

return p