Module:Items

From Melvor Idle
Revision as of 16:30, 23 September 2020 by Falterfire (talk | contribs) (Initial implementation of the automatic equipment table)

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[1] 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'

   --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="2"|'
  else
    result = result..'\r\n!colspan="1"|'
  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..'!style="padding:0 1em 0 0.5em;"|Item'
  --Weapons have Attack Speed here
  if isWeaponType then
    result = result..'!style="padding:0 1em 0 0.5em;"|Attack Speed'
  end
  --Attack bonuses
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Strength bonuses
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Defence bonuses
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'!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..'!style="padding:0 1em 0 0.5em;"|Two Handed?'
    result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  else
    result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
    result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  --Then Ranged/Magic requirements
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --And finally Sources
  result = result..'!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, item in pairs(ItemData) do
    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.5em 0 0.5em;"|'..Icons.Icon({item.name, type='item'})
        result = result..'| 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..'color:lightgreen;'
          elseif statValue < 0 then
            result = result..'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;'
          if statValue > 0 then
            result = result..'color:lightgreen;'
          elseif statValue < 0 then
            result = result..'color:lightpink;'
          end
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --Finally, the Sources
        result = result..'| 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.5em 0 0.5em;"|'..Icons.Icon({item.name, type='item'})
        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..'color:lightgreen;'
          elseif statValue < 0 then
            result = result..'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 statValue > 0 then
            result = result..'color:lightgreen;'
          elseif statValue < 0 then
            result = result..'color:lightpink;'
          end
          result = result..'"|'..Shared.formatnum(statValue)
          if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
        end
        --Finally, the Sources
        result = result..'| style ="text-align: right;white-space: nowrap;padding: 0 0.5em 0 0.5em;" |'
        result = result..p._getItemSources(item)
      end
    end
  end

  result = result..'|}'
  return result
end

return p