Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
Added 'no item' drop chance to NPC table
(Added manual exception for Bird Nest Potions to fix the PotionNav template)
(Added 'no item' drop chance to NPC table)
(20 intermediate revisions by 3 users not shown)
Line 1: Line 1:
--Some skills have their own modules:
--Some skills have their own modules:
--Module:Magic and Module:Prayer being the current two examples.
--Module:Magic for Magic
--Module:Prayer for Prayer
--Module:Agility for Agility
--Module:Skills/Gathering for Mining, Fishing, Woodcutting
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc.


local p = {}
local p = {}


local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')
local SkillData = mw.loadData('Module:Skills/data')
local Constants = mw.loadData('Module:Constants/data')
local Constants = mw.loadData('Module:Constants/data')
Line 21: Line 26:
   end
   end
   return nil
   return nil
end
function p.getSkillName(skillID)
  for skName, ID in Shared.skpairs(Constants.skill) do
    if ID == skillID then
      return skName
    end
  end
  return nil
end
function p.getThievingNPCByID(ID)
  local result = Shared.clone(SkillData.Thieving[ID + 1])
  if result ~= nil then
    result.id = ID
  end
  return result
end
function p.getThievingNPC(name)
  local result = nil
  for i, npc in pairs(SkillData.Thieving) do
    if name == npc.name then
      result = Shared.clone(npc)
      result.id = i - 1
      break
    end
  end
  return result
end
function p.getThievingNPCStat(frame)
  local args = frame.args ~= nil and frame.args or frame
  local npcName = args[1]
  local statName = args[2]
  local npc = p.getThievingNPC(npcName)
  if npc == nil then
    return 'ERROR: Failed to find Thieving NPC with name ' .. name .. '[[Category:Pages with script errors]]'
  end
 
  return p._getThievingNPCStat(npc, statName)
end
function p._getThievingNPCStat(npc, stat)
  local result = npc[stat]
  -- Overrides below
  if stat == 'maxHit' then
    result = result * 10
  elseif stat == 'lootTable' then
    return p._formatLootTable(npc['lootTable'], 0.75)
  elseif stat == 'requirements' then
  if npc['level'] ~= nil then
    result = Icons._SkillReq('Thieving', npc['level'], true)
  else
    result = 'None'
  end
  end
  return result
end
function p._formatLootTable(lootTableIn, chanceMultIn)
  -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
  if Shared.tableCount(lootTableIn) == 0 then
    return ''
  end
  local chanceMult = (chanceMultIn or 1) * 100
  local lootTable = Shared.clone(lootTableIn)
  -- Sort table from most to least common drop
  table.sort(lootTable, function(a, b)
                          if a[2] == b[2] then
                            return a[1] < b[1]
                          else
                            return a[2] > b[2]
                          end
                        end)
  local totalWeight = 0
  for i, drop in pairs(lootTable) do
    totalWeight = totalWeight + drop[2]
  end
  if totalWeight == 0 then
    return ''
  end
  -- Get the length (in characters) of the largest drop chance so that they can be right aligned
  -- [4/16/21]: Adding info for no drop
  local maxDropLen = math.max(string.len(Shared.round(25, 2, 2)), string.len(Shared.round(lootTable[1][2] / totalWeight * chanceMult, 2, 2)))
  local returnPart = {}
  table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(Shared.round(25, 2, 2))) * 2)) .. '25.00% No Item')
  for i, drop in pairs(lootTable) do
    local item, itemText, dropChance = Items.getItemByID(drop[1]), nil, Shared.round(drop[2] / totalWeight * chanceMult, 2, 2)
    if item == nil then
      itemText = 'Unknown'
    else
      itemText = Icons.Icon({item.name, type='item'})
    end
    table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(dropChance)) * 2)) .. dropChance .. '% ' .. itemText)
  end
  return table.concat(returnPart, '\r\n')
end
function p.getThievingNPCTable()
  local returnPart = {}
  -- Create table header
  table.insert(returnPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(returnPart, '|- class="headerRow-0"\r\n!Target!!Name!!' .. Icons.Icon({'Thieving', type='skill', notext=true}).. ' Level!!Experience!!Max Hit!!Max Coins')
 
  local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    local linkText = npc.name
    if linkOverrides[npc.name] ~= nil then
      linkText = linkOverrides[npc.name] .. '|' .. npc.name
    end
    table.insert(returnPart, '|-\r\n|style="text-align: left;" |' .. Icons.Icon({npc.name, type='thieving', size=50, notext=true}))
    table.insert(returnPart, '|style="text-align: left;" |[[' .. linkText .. ']]')
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'level'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'xp'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxHit'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxCoins'))
  end
  table.insert(returnPart, '|}')
  return table.concat(returnPart, '\r\n')
end
function p.getThievingNavbox()
  local returnPart = {}
  -- Create table header
  table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
  table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]')
  table.insert(returnPart, '|-\r\n|')
 
  local npcList = {}
  local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    local linkText = npc.name
    if linkOverrides[npc.name] ~= nil then
      linkText = linkOverrides[npc.name] .. '|' .. npc.name
    end
    table.insert(npcList, Icons.Icon({npc.name, type='thieving', notext=true}) .. ' [[' .. linkText .. ']]')
  end
  table.insert(returnPart, table.concat(npcList, ' • '))
  table.insert(returnPart, '|}')
  return table.concat(returnPart, '\r\n')
end
end


Line 119: Line 277:
end
end


function p.getMiningTable(frame)
function p.getFarmingFoodTable(frame)
   local result = '{|class="wikitable sortable stickyHeader"'
   local result = '{| class="wikitable sortable stickyHeader"'
   result = result..'\r\n|- class="headerRow-0"'
   result = result..'\r\n|- class="headerRow-0"'
   result = result..'\r\n!colspan=2|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
   result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
   result = result..'!!XP!!Respawn Time!!Ore Value'
   result = result..'!!Healing!!Value'
   local mineData = Shared.clone(SkillData.Mining)
 
   table.sort(mineData, function(a, b) return a.level < b.level end)
   local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end)
   for i, oreData in Shared.skpairs(mineData) do
 
     local ore = Items.getItemByID(oreData.ore)
   table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
     result = result..'\r\n|-\r\n|'..Icons.Icon({ore.name, type='item', size='50', notext=true})..'||'..ore.name
 
    result = result..'||style="text-align:right"|'..oreData.level..'||style="text-align:right"|'..ore.miningXP
   for i, item in Shared.skpairs(itemArray) do
    result = result..'||style="text-align:right" data-sort-value="'..oreData.respawnInterval..'"|'
     local crop = Items.getItemByID(item.grownItemID)
     result = result..Shared.timeString(oreData.respawnInterval / 1000, true)
     if crop.healsFor ~= nil and crop.healsFor > 0 then
     result = result..'||data-sort-value="'..ore.sellsFor..'"|'..Icons.GP(ore.sellsFor)
      result = result..'\r\n|-'
      result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
      result = result..'||style="text-align:right;"|'..item.farmingLevel
      result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
      result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
     end
  end
 
  result = result..'\r\n|}'
 
  return result
end
 
function p.getFarmingPlotTable(frame)
  local areaName = frame.args ~= nil and frame.args[1] or frame
  local patches = nil
  for i, area in Shared.skpairs(SkillData.Farming.Patches) do
    if area.areaName == areaName then
      patches = area.patches
      break
    end
  end
  if patches == nil then
    return "ERROR: Invalid area name.[[Category:Pages with script errors"
  end
 
  local result = '{|class="wikitable"'
  result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
 
  for i, patch in Shared.skpairs(patches) do
    result = result..'\r\n|-\r\n|'..i
     result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
    if patch.cost == 0 then
      result = result..'||Free'
    else
      result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
    end
   end
   end


Line 166: Line 360:
end
end


function p.getSpecialFishingTable(frame)
function p.getSmithingTable(frame)
   local lootValue = 0
   local tableType = frame.args ~= nil and frame.args[1] or frame
   local totalWt = Items.specialFishWt
   local bar = nil
  if tableType ~= 'Smelting' then
    bar = Items.getItem(tableType)
    if bar == nil then
      return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
    elseif bar.type ~= 'Bar' then
      return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
    end
  end


   local result = ''
   local smithList = {}
   result = result..'\r\n{|class="wikitable sortable"'
   for i, item in pairs(ItemData.Items) do
  result = result..'\r\n!Item'
    if item.smithingLevel ~= nil then
  result = result..'!!Price!!colspan="2"|Chance'
      if tableType == 'Smelting' then
        if item.type == 'Bar' then
          table.insert(smithList, item)
        end
      else
        for j, req in pairs(item.smithReq) do
          if req.id == bar.id then
            table.insert(smithList, item)
          end
        end
      end
    end
  end


   --Sort the loot table by weight in descending order
   local result = '{|class="wikitable sortable stickyHeader"'
  table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
  result = result..'\r\n|-class="headerRow-0"'
  for i, row in pairs(Items.specialFishLoot) do
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
    local thisItem = Items.getItemByID(row[1])
  --Adding value/bar for things other than smelting
    result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
  if bar ~= nil then result = result..'!!Value/Bar' end
    result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
    result = result..'|'..Icons.GP(thisItem.sellsFor)


     local dropChance = (row[2] / totalWt) * 100
  table.sort(smithList, function(a, b)
     result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
                          if a.smithingLevel ~= b.smithingLevel then
     result = result..'|'..Shared.fraction(row[2], totalWt)
                            return a.smithingLevel < b.smithingLevel
     result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
                          else
     lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
                            return a.name < b.name
                          end end)
  for i, item in Shared.skpairs(smithList) do
    result = result..'\r\n|-'
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
     local qty = item.smithingQty ~= nil and item.smithingQty or 1
    if qty > 1 then
      result = result..item.smithingQty..'x '
    end
    result = result..'[['..item.name..']]'
     result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel)
    result = result..'||'..item.smithingXP
    local totalValue = item.sellsFor * qty
    result = result..'||data-sort-value="'..totalValue..'"|'..Icons.GP(item.sellsFor)
    if qty > 1 then
      result = result..' (x'..qty..')'
    end
     result = result..'||'
    local barQty = 0
    for i, mat in Shared.skpairs(item.smithReq) do
      matItem = Items.getItemByID(mat.id)
      if i > 1 then result = result..', ' end
      result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty, notext=true})
      if bar ~= nil and mat.id == bar.id then
        barQty = mat.qty
      end
    end
    --Add the data for the value per bar
     if bar ~= nil then
      if barQty == 0 then
        result = result..'||data-sort-value="0"|N/A'
      else
        local barVal = totalValue / barQty
        result = result..'||data-sort-value="'..barVal..'"|'..Icons.GP(Shared.round(barVal, 1, 1))
      end
     end
   end
   end
   result = result..'\r\n|}'
   result = result..'\r\n|}'
   result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))
   return result
end


function p.getFiremakingTable(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
  result = result..'\r\n|-class="headerRow-0"'
  result = result..'\r\n!colspan="2"|Logs!!'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level'
  result = result..'!!XP!!Burn Time!!XP/s!!Bonfire Bonus!!Bonfire Time'
  for i, logData in Shared.skpairs(SkillData.Firemaking) do
    result = result..'\r\n|-'
    local name = Shared.titleCase(logData.type..' Logs')
    result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})
    result = result..'||[['..name..']]'
    result = result..'||style ="text-align: right;"|'..logData.level
    result = result..'||style ="text-align: right;"|'..logData.xp
    local burnTime = logData.interval / 1000
    local XPS = logData.xp / burnTime
    result = result..'||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)
    result = result..'||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2)
    result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireBonus..'"|'..logData.bonfireBonus..'%'
    result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireInterval..'"|'..Shared.timeString(logData.bonfireInterval / 1000, true)
  end
  result = result..'\r\n|}'
   return result
   return result
end
end


return p
return p