Module:Items: Difference between revisions

From Melvor Idle
No edit summary
(Fixed issue with getting item sources)
Line 338: Line 338:
   local ammoTypeStr = args.ammoType
   local ammoTypeStr = args.ammoType
   local category = args.category ~= nil and args.category or 'Combat'
   local category = args.category ~= nil and args.category or 'Combat'
  local i = 0
  local j = 0


   --Find out what Ammo Type we're working with
   --Find out what Ammo Type we're working with
Line 430: Line 432:


   --And with all the header out of the way, finally time to actually build the table itself.
   --And with all the header out of the way, finally time to actually build the table itself.
   for i, item in pairs(ItemData) do
   for i, itemBase in pairs(ItemData) do
    local item = Shared.clone(itemBase)
    item.id = i - 1
     local listItem = false
     local listItem = false
     if isWeaponType then
     if isWeaponType then

Revision as of 16:54, 23 September 2020

Lua module for generating various item tables. Pulls data from Module:GameData/data


local p = {}

local MonsterData = mw.loadData('Module:Monsters/data')
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')
local Constants = mw.loadData('Module:Constants/data')

local Shared = require('Module:Shared')
local Icons = require('Module:Icons')

local EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon'}
local OtherShopItems = {'Cooking Gloves', 'Mining Gloves', 'Gem Gloves', 'Smithing Gloves', 'Thieving Gloves'}

function p.getItemByID(ID)
  local result = Shared.clone(ItemData[ID + 1])
  if result ~= nil then
    result.id = ID
  end
  return result
end

function p.getItem(name)
  local result = nil
  for i, item in pairs(ItemData) do
    if(item.name == name) then
      result = Shared.clone(item)
      --Make sure every item has an id, and account for Lua being 1-index
      result.id = i -1
    end
  end
  return result
end

function p._getItemStat(item, StatName, ZeroIfNil)
  local result = item[StatName]
  --Special Overrides:
  if StatName == 'stabAttackBonus' then
    if item.attacBonus == nil then 
      result = nil
    else
      result = item.attackBonus[1]
    end
  elseif StatName == 'slashAttackBonus' then
    if item.attackBonus == nil then 
      result = nil
    else
      result = item.attackBonus[2]
    end
  elseif StatName == 'blockAttackBonus' then
    if item.attackBonus == nil then 
      result = nil
    else
      result = item.attackBonus[3]
    end
  elseif StatName == 'attackType' then
    result = p._getWeaponAttackType(item)
  end
  if result == nil and ZeroIfNil then result = 0 end
  return result
end

function p.getItemStat(frame)
  local args = frame.args ~= nil and frame.args or frame
  local ItemName = args[1]
  local StatName = args[2]
  local ZeroIfNil = args.ForceZero ~= nil and args.ForceZero ~= '' and args.ForceZero ~= 'false'
  local item = p.getItem(ItemName)
  if item == nil then
    return "ERROR: No item named "..ItemName.." exists in the data module"
  end
  return p._getItemStat(item, StatName, ZeroIfNil)
end

function p._getWeaponAttackType(item)
  if item.type == 'Weapon' then
    return Icons.Icon({'Melee', nolink='true'})
  elseif item.type == 'Ranged Weapon' then
    return Icons.Icon({'Ranged', type='skill', nolink='true'})
  elseif item.type == 'Magic Staff' or item.type == 'Magic Wand' then
    return Icons.Icon({'Magic', type='skill', nolink='true'})
  else
    return "Invalid"
  end
end


function p.getWeaponAttackType(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = p.getItem(itemName)
  if item == nil then
    return "ERROR: No item named "..ItemName.." exists in the data module"
  end
  return p._getWeaponAttackType(item)
end

function p.getPotionTable(frame)
  local potionName = frame.args ~= nil and frame.args[1] or frame
  local tiers = {'I', 'II', 'III', 'IV'}

  local result = '{| class="wikitable"'
  result = result..'\r\n!Potion!!Tier!!Charges!!Effect'

  local tier1potion = p.getItem(potionName..' I')
  for i, tier in pairs(tiers) do
    local tierName = potionName..' '..tier
    local potion = p.getItemByID(tier1potion.id + i - 1)
    if potion == nil then
       mw.log("Failed to get tier "..tier)
    else
      result = result..'\r\n|-'
      result = result..'\r\n|'..Icons.Icon({tierName, type='item', notext='true', size='60'})
      result = result..'||'..'[['..tierName..'|'..tier..']]'
      result = result..'||'..potion.potionCharges..'||'..potion.description
    end
  end

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

function p.getCreationTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = p.getItem(itemName)
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end

  local skill = ''
  local time = 0
  local lvl = 0
  local xp = 0
  local qty = nil
  local req = {}
  --First figure out what skill is used to make this...
  if item.smithingLevel ~= nil then
    skill = 'Smithing'
    lvl = item.smithingLevel
    xp = item.smithingXP
    req = item.smithReq
    qty = item.smithingQty
    time = 2
  elseif item.craftingLevel ~= nil then
    skill = 'Crafting'
    lvl = item.craftingLevel
    xp = item.craftingXP
    req = item.craftReq
    qty = item.craftQty
    time = 3
  elseif item.runecraftingLevel ~= nil then
    skill = 'Runecrafting'
    lvl = item.runecraftingLevel
    xp = item.runecraftingXP
    req = item.runecraftReq
    qty = item.runecraftQty
    time = 2
  elseif item.fletchingLevel ~= nil then
    skill = 'Fletching'
    lvl = item.fletchingLevel
    xp = item.fletchingXP
    req = item.fletchReq
    qty = item.fletchQty
    time = 2
  elseif item.herbloreReq ~= nil then
    skill = 'Herblore'
    req = item.herbloreReq
    --Currently using 'herbloreMasteryID' as shorthand to find details, could be a better method
    local potionID = item.herbloreMasteryID
    local potionData = SkillData.Herblore.ItemData[potionID + 1]
    lvl = potionData.herbloreLevel
    xp = potionData.herbloreXP
    time = 2
  else
    return "Failed to find creation requirements for this (Possibly the module isn't properly updated for this skill)"
  end
  if qty == nil then qty = 1 end

  local result = '{|class="wikitable"'
  result = result..'\r\n!colspan="2"|Item Creation\r\n|-'
  result = result..'\r\n|-\r\n!style="text-align: right;"|Requirements'
  result = result..'\r\n|'..Icons.Icon({skill, type="skill", notext="true"}).." '''"..lvl.."'''"
  result = result..'\r\n|-\r\n!style="text-align: right;"|Materials\r\n|'
  for i, mat in pairs(req) do
    if i > 1 then result = result..'<br/>' end
    local matItem = p.getItemByID(mat.id)
    if matItem == nil then
      result = result..mat.qty..'x ?????'
    else
      result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty})
    end
  end
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Quantity'
  result = result..'\r\n|'..qty
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Experience'
  result = result..'\r\n|'..xp..' XP'
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Creation Time'
  result = result..'\r\n|'..time..'.0 s'
  result = result..'\r\n|}'

  return result
end

function p._getItemSources(item)
  local result = ''
  --Alright, time to go through all the ways you can get an item...
  --First up: Can we kill somebody and take theirs?
  local killFound = false
  for i, monster in pairs(MonsterData.Monsters) do
    if monster.lootTable ~= nil then
      for j, loot in pairs(monster.lootTable) do
        if loot[1] == item.id then
          if killFound then
            result = result..','..Icons.Icon({monster.name, type="monster", notext="true"})
          else
            result = result..'Killing: '..Icons.Icon({monster.name, type="monster", notext="true"})
            killFound = true
          end
        end
      end
    end
  end

  --Next: Can we find it in a box?
  local boxFound = false
  for i, chest in pairs(ItemData) do
    if chest.dropTable ~= nil then
      for j, loot in pairs(chest.dropTable) do
        if loot[1] == item.id then
          if boxFound then
            result = result..','..Icons.Icon({chest.name, type="item", notext="true"})
          else
            if string.len(result) > 0 then result = result..'\r\n<br/>' end
            result = result..'Opening: '..Icons.Icon({chest.name, type="item", notext="true"})
            boxFound = true
          end
        end
      end
    end
  end

  --Next: Can we take it from somebody else -without- killing them?
  for i, npc in pairs(SkillData.Thieving) do
    if npc.lootTable ~= nil then
      for j, loot in pairs(npc.lootTable) do
        if loot[1] == item.id then
          if string.len(result) > 0 then result = result..'\r\n<br/>' end
          result = result..'Pickpocketing: '..Icons.Icon({npc.name, type="thieving", notext="true"})
          boxFound = true
        end
      end
    end
  end

  --Next: Can we get to it via upgrading?
  local upgradeFound = false
  if item.itemsRequired ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..'Upgrading: '
    for i, req in pairs(item.itemsRequired) do
      local reqItem = p.getItemByID(req[1])
      if reqItem.canUpgrade then
        if upgradeFound then result = result..', ' else upgradeFound = true end
        result = result..Icons.Icon({reqItem.name, type='item', notext='true'})
      end
    end
  end

  --If all else fails, I guess we should check if we can make it ourselves
  --SmithCheck:
  if item.smithingLevel ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Smithing", item.smithingLevel)
  end

  --CraftCheck:
  if item.craftingLevel ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Crafting", item.craftingLevel)
  end

  --FletchCheck:
  if item.fletchingLevel ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Fletching", item.fletchingLevel)
  end

  --RunecraftCheck:
  if item.runecraftingLevel ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Runecrafting", item.runecraftingLevel)
  end

  --FishCheck:
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) or item.category == "Gem" then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Fishing", 1)
  elseif item.category == 'Fishing' then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..Icons._SkillReq("Fishing", item.fishingLevel)
  end

  --Finally there are some weird exceptions:
  --Shop items
  if item.slayerCost ~= nil or item.buysFor ~= nil then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..'[[Shop]]'
  end

  --Special Shop Items (ie gloves, which don't technically exist in the shop)
  if Shared.contains(OtherShopItems, item.name) then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..'[[Shop]]'
  end

  --Easter Eggs (manual list 'cause don't have a better way to do that)
  if Shared.contains(EasterEggs, item.name) then
    if string.len(result) > 0 then result = result..'\r\n<br/>' end
    result = result..'[[Easter Eggs]]'
  end

  return result
end

function p.getItemSources(frame)
  local itemName = frame.args ~= nil and frame[1] or frame
  local item = p.getItem(itemName)
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end

  return p._getItemSources(item)
end

function p.getEquipmentTable(frame)
  local args = frame.args ~= nil and frame.args or frame
  local type = args.type
  local tier = args.tier
  local slotStr = args.slot
  local ammoTypeStr = args.ammoType
  local category = args.category ~= nil and args.category or 'Combat'
  local i = 0
  local j = 0

   --Find out what Ammo Type we're working with
  local ammoType = nil
  if ammoTypeStr ~= nil then
    if ammoTypeStr == "Arrows" then
      ammoType = 0
    elseif ammoTypeStr == 'Bolts' then
      ammoType = 1
    elseif ammoTypeStr == 'Javelins' then
      ammoType = 2
    elseif ammoTypeStr == 'Throwing Knives' then
      ammoType = 3
    end
  end

  --Find out what slot we're working with
  local slot = nil
  if slotStr ~= nil then
    slot = Constants.equipmentSlot[slotStr]
  end
  mw.log("Type = "..(type ~= nil and type or '')..", Slot = "..(slot ~= nil and slot or '')..", AmmoType = "..(ammoType ~= nil and ammoType or ''))
  

  --Getting some lists set up here that will be used later
  --First, the list of columns used by both weapons & armor
  local statColumns = {'slashAttackBonus', 'stabAttackBonus','blockAttackBonus','rangedAttackBonus', 'magicAttackBonus', 'strengthBonus', 'rangedStrengthBonus', 'magicDamageBonus', 'defenceBonus', 'rangedDefenceBonus', 'magicDefenceBonus'}
  --Then the lists for just weapons/just armor
  local weaponStatColumns = {'attackLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'} 
  local armorStatColumns = {'damageReduction', 'defenceLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'}
  --Then the list of weapon types
  local weaponTypes = {'Magic Staff', 'Magic Wand', 'Ranged Weapon', 'Weapon'}

  local isWeaponType = Shared.contains(weaponTypes, type)
  
  --Alright, let's start the table by building the shared header
  local result = '{| class="wikitable sortable stickyHeader"\r\n|-class="headerRow-0"'
  if isWeaponType then
    --Weapons have an extra column here for Attack Speed
    result = result..'\r\n!colspan="3"|'
  else
    result = result..'\r\n!colspan="2"|'
  end
  result = result..'\r\n!colspan="5"style="padding:0 0.5em 0 0.5em;"|Attack Bonus'
  result = result..'\r\n!colspan="2"style="padding:0 0.5em 0 0.5em;"|Strength Bonus'
  result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|% Damage Bonus'
  result = result..'\r\n!colspan="3"style="padding:0 0.5em 0 0.5em;"|Defence Bonus'
  if isWeaponType then
    --Weapons have an extra columns here for "Two Handed?"
    result = result..'\r\n!colspan="1"|'
  else
    --Only armor pieces have DR right now, so ignore that column for weapons
    result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|Damage Reduction'
  end
  result = result..'\r\n!colspan="3"style="padding:0 0.5em 0 0.5em;"|Levels Required'
  result = result..'\r\n!colspan="1"|'
  --One header row down, one to go
  result = result..'\r\n|-class="headerRow-1"'
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Item'
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Name'
  --Weapons have Attack Speed here
  if isWeaponType then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Attack Speed'
  end
  --Attack bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Strength bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Defence bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Damage Reduction/Defence Req for armor, 2-handed/Attack Req for weapons
  if isWeaponType then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Two Handed?'
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  else
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  --Then Ranged/Magic requirements
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --And finally Sources
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Sources'

  --And with all the header out of the way, finally time to actually build the table itself.
  for i, itemBase in pairs(ItemData) do
    local item = Shared.clone(itemBase)
    item.id = i - 1
    local listItem = false
    if isWeaponType then
      listItem = item.type == type and item.category == category
      if ammoType ~= nil then listItem = listItem and item.ammoTypeRequired == ammoType end
      if listItem then
        result = result..'\r\n|-'
        result = result..'\r\n|style ="text-align: left;padding: 0 0 0 0;"|'..Icons.Icon({item.name, type='item', size=50, notext=true})
        result = result..'\r\n|style ="text-align: left;padding: 0 0.5em 0 0.5em;"|[['..item.name..']]'
        result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;" |'..Shared.formatnum(item.attackSpeed)
        for j, statName in pairs(statColumns) do
          local statValue = p._getItemStat(item, statName, true)
          result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
          if statValue > 0 then
            result = result..'background-color:lightgreen;'
          elseif statValue < 0 then
            result = result..'background-color:lightpink;'
          end
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --That's the first list out of the way, now for 2-Handed
        result = result..'\r\n| style ="text-align: right;"|'
        if item.isTwoHanded then result = result..'Yes' else result = result..'No' end
        --Now the weapon exclusive columns
        for j, statName in pairs(weaponStatColumns) do
          local statValue = p._getItemStat(item, statName, true)
          result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --Finally, the Sources
        result = result..'\r\n| style ="text-align: right;white-space: nowrap;padding: 0 0.5em 0 0.5em;" |'
        result = result..p._getItemSources(item)
      end
    else
      --Now for handling armor
      listItem = item.type == type and item.category == category
      if ammoType ~= nil then listItem = listItem and item.ammoType == ammoType end
      if slot ~= nil then listItem = listItem and item.equipmentSlot == slot end

      if listItem then
        result = result..'\r\n|-'
        result = result..'\r\n|style ="text-align: left;padding: 0 0 0 0;"|'..Icons.Icon({item.name, type='item', size=50, notext=true})
        result = result..'\r\n|style ="text-align: left;padding: 0 0.5em 0 0.5em;"|[['..item.name..']]'
        for j, statName in pairs(statColumns) do
          local statValue = p._getItemStat(item, statName, true)
          result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
          if statValue > 0 then
            result = result..'background-color:lightgreen;'
          elseif statValue < 0 then
            result = result..'background-color:lightpink;'
          end
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --That's the first list out of the way, now for armor specific things
        for j, statName in pairs(armorStatColumns) do
          local statValue = p._getItemStat(item, statName, true)
          result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
          if j == 1 then
            if statValue > 0 then
              result = result..'background-color:lightgreen;'
            elseif statValue < 0 then
              result = result..'background-color:lightpink;'
            end
          end
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --Finally, the Sources
        result = result..'\r\n| style ="text-align: right;white-space: nowrap;padding: 0 0.5em 0 0.5em;" |'
        result = result..p._getItemSources(item)
      end
    end
  end

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

return p