Anonymous

Module:Constants: Difference between revisions

From Melvor Idle
Updated Arch Tool Level display, Fixed error on nil modifiers in getModifiersDifference
m (getCombatStyleName: Amend error message)
(Updated Arch Tool Level display, Fixed error on nil modifiers in getModifiersDifference)
 
(131 intermediate revisions by 7 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local ConstantData = mw.loadData('Module:Constants/data')
local ItemData = mw.loadData('Module:Items/data')


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


--Just hardcoding these because I guess that's where we're at
--Just hardcoding these because I guess that's where we're at
--getModifierSkills still needs skills, otherwise this can be removed
local modifierTypes = {
local modifierTypes = {
  ["increasedGlobalAccuracy"] = { text =  "+{V}% Global Accuracy", },
["MeleeStrengthBonus"] = { text = "{V}% Melee Strength Bonus from Equipment", skills = {'Combat'} },
  ["increasedMeleeAccuracyBonus"] = { text =  "+{V}% Melee Accuracy Bonus", },
["DamageToDungeonMonsters"] = { text = "{V}% Damage To Dungeon Monsters", skills = {'Combat'} },
  ["increasedMeleeStrengthBonus"] = { text =  "+{V}% Melee Strength Bonus", },
["GlobalMasteryXP"] = { text = "{V}% Global Mastery XP", skills = {'Woodcutting', 'Fishing', 'Firemaking', 'Cooking', 'Mining', 'Smithing', 'Thieving', 'Farming', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Agility', 'Summoning', 'Astrology', 'Archaeology'} },
  ["increasedMeleeEvasion"] = { text =  "+{V}% Melee Evasion", },
["ChanceRandomPotionHerblore"] = { text = "{V}% chance to gain a second potion of a random tier", skills = {'Herblore'} },
  ["increasedRangedAccuracyBonus"] = { text =  "+{V}% Ranged Accuracy Bonus", },
["FlatPrayerCostReduction"] = { text = "{V} Prayer Point Cost for Prayers (Prayer Point cost cannot go below 1)", inverseSign = true, skills = {'Prayer'} },
  ["increasedRangedStrengthBonus"] = { text =  "+{V}% Ranged Strength Bonus", },
["MinEarthSpellDmg"] = { text = "{VX} Min Earth Spell Dmg", skills = {'Magic'} },
  ["increasedRangedEvasion"] = { text =  "+{V}% Ranged Evasion", },
["SlayerTaskLength"] = { text = "{V}% Slayer Task Length/Qty", skills = {'Slayer'} },
 
["ChanceToDoubleLootCombat"] = { text = "{V}% Chance To Double Loot in Combat", skills = {'Combat'} },
["GPFromAgility"] = { text = "{V}% GP From Agility", skills = {'Agility'} },
["SkillXP"] = { text = "{V}% {SV0} Skill XP" },
["MiningNodeHP"] = { text = "{V} Mining Node HP", skills = {'Mining'} },
["FarmingYield"] = { text = "{V}% Farming Yield", skills = {'Farming'} },
["GPFromMonstersFlat"] = { text = "{V} GP From Monsters", skills = {'Combat'} },
["GlobalPreservationChance"] = { text = "{V}% Chance to Preserve Resources in Skills" },
["RunePreservation"] = { text = "{V}% Rune Preservation", skills = {'Magic'} },
["MaxHitpoints"] = { text = "{VX} Maximum Hitpoints", skills = {'Combat', 'Hitpoints'} },
["ChanceToDoubleItemsSkill"] = { text = "{V}% Chance to Double Items in {SV0}" },
["autoSlayerUnlocked"] = { text = "{V} Auto Slayer Unlocked", skills = {'Slayer'} },
["HitpointRegeneration"] = { text = "{V}% Hitpoint Regeneration", skills = {'Combat', 'Hitpoints'} },
["PotionChargesFlat"] = { text = "{V} Charges per Potion" },
["SkillInterval"] = { text = "{VMS}s {SV0} Interval", isIncreaseNegative = true, isSkill = true },
["BankSpace"] = { text = "{V} Bank Space" },
["MinHitBasedOnMaxHit"] = { text = "{V}% of Maximum Hit added to Minimum Hit", skills = {'Combat'} },
["
}
}


--Difficulties are hard coded which is dumb but means hardcoding them here too
 
local Difficulties = {
function p.getTriangleAttribute(attribute, attackerStyle, targetStyle, modeName)
    [0] = 'Very Easy',
if type(attribute) ~= 'string' then
    [1] = 'Easy',
error("Parameter 'attribute' must be a string", 2)
    [2] = 'Medium',
elseif type(attackerStyle) ~= 'string' then
    [3] = 'Hard',
error("Parameter 'attackerStyle' must be a string", 2)
    [4] = 'Very Hard',
elseif type(targetStyle) ~= 'string' then
    [5] = 'Elite'}
error("Parameter 'targetStyle' must be a string", 2)
elseif type(modeName) ~= 'string' then
error("Parameter 'modeName' must be a string", 2)
end
local mode = GameData.getEntityByName('gamemodes', modeName)
if mode == nil then
error("Invalid gamemode '" .. modeName .. "'", 2)
end
local attStyle, targStyle = string.lower(attackerStyle), string.lower(targetStyle)
local validStyles = { 'magic', 'melee', 'ranged' }
if not Shared.contains(validStyles, string.lower(attStyle)) then
error("Invalid value for parameter 'attackerStyle'", 2)
elseif not Shared.contains(validStyles, string.lower(targStyle)) then
error("Invalid value for parameter 'targetStyle'", 2)
end
local combatTriangle = GameData.getEntityByID('combatTriangles', mode.combatTriangle)
if combatTriangle == nil then
error("No such combat triangle: " .. mode.combatTriangle)
end
local attrData = combatTriangle[attribute]
if attrData == nil then
error("No such attribute: " .. attribute)
else
return attrData[attStyle][targStyle]
end
end
 
function p.getTriangleDamageModifier(attackerStyle, targetStyle, mode)
return p.getTriangleAttribute('damageModifier', attackerStyle, targetStyle, mode)
end
 
--Syntax is like p.getTriangleDRModifier('Melee', 'Ranged', 'Normal')
--Returns a multiplier that can be multiplied with base DR to get the post-Triangle result
function p.getTriangleDRModifier(attackerStyle, targetStyle, mode)
return p.getTriangleAttribute('reductionModifier', attackerStyle, targetStyle, mode)
end


function p.getDifficultyString(difficulty)
function p.getDifficultyString(difficulty)
  return Difficulties[difficulty]
return GameData.rawData.combatAreaDifficulties[difficulty + 1]
end
end


function p.getSkillName(skillID)
function p.getSkillName(skillID)
  for skName, ID in Shared.skpairs(ConstantData.skill) do
local skill = GameData.getSkillData(skillID)
    if ID == skillID then
if skill ~= nil then
      return skName
return skill.name
    end
end
  end
  return nil
end
end


function p.getSkillID(skillName)
function p.getSkillID(skillName)
  return ConstantData.skill[skillName]
for i, skillData in ipairs(GameData.rawData.skillData) do
if skillData.data.name == skillName then
return skillData.skillID
end
end
end
end


function p.getEquipmentSlotName(id)
function p.getEquipmentSlotName(id)
  for slotName, i in Shared.skpairs(ConstantData.equipmentSlot) do
local slotData = GameData.getEntityByID('equipmentSlots', id)
    if i == id then
if slotData ~= nil then
      return slotName
return slotData.name
    end
end
  end
  return 'Invalid'
end
end


function p.getEquipmentSlotID(name)
function p.getEquipmentSlotID(name)
  return ConstantData.equipmentSlot[name]
local slotData = GameData.getEntityByName('equipmentSlots', name)
if slotData ~= nil then
return slotData.id
end
end
end


function p.getCombatStyleName(styleNum)
function p.getCombatStyleName(styleNum)
  for name, num in Shared.skpairs(ConstantData.attackType) do
if type(styleNum) == 'number' then
    if num == styleNum then
local styleName = GameData.rawData.attackTypes[styleNum]
      return name
if styleName ~= nil then
    end
return Shared.titleCase(styleName)
  end
end
  return "ERROR: Invalid combat style[[Category:Pages with script errors]]"
elseif type(styleNum) == 'string' and type(GameData.rawData.attackTypes[string.lower(styleNum)]) == 'number' then
return Shared.titleCase(styleNum)
end
return Shared.printError('Invalid combat style')
end
 
 
--- Slayer functions
--
function p.getSlayerTierByID(tierID)
if type(tierID) ~= 'number' then
return nil
else
return GameData.rawData.slayerTiers[tierID + 1]
end
end
 
function p.getSlayerTier(name)
return GameData.getEntityByProperty('slayerTiers', 'display', name)
end
end


function p.getSlayerTierName(tier)
function p.getSlayerTierByLevel(level)
  for name, num in Shared.skpairs(ConstantData.slayerTier) do
if type(level) ~= 'number' or level < 1 then
    if num == tier then
return Shared.printError('Invalid Slayer level')
      return name
end
    end
  end
for i, tier in ipairs(GameData.rawData.slayerTiers) do
  return "ERROR: Invalid Slayer tier[[Category:Pages with script errors]]"
if tier.minLevel <= level and (tier.maxLevel == nil or tier.maxLevel >= level) then
return tier
end
end
end
 
--
-- the following functions just return subsets of the slayer functions above
--
 
function p.getSlayerTierName(tierID)
if type(tierID) == 'number' then
local tier = p.getSlayerTierByID(tierID)
if tier ~= nil then
return tier.display
end
end
return Shared.printError('Invalid Slayer tier')
end
end


function p.getSlayerTierNameByLevel(lvl)
function p.getSlayerTierNameByLevel(lvl)
  for i, tier in Shared.skpairs(ConstantData.Slayer.Tiers) do
local tier = p.getSlayerTierByLevel(lvl)
    if tier.minLevel <= lvl and (tier.maxLevel >= lvl or tier.maxLevel == -1) then
if type(tier) == 'table' then
      return tier.display
return tier.display
    end
else
  end
return Shared.printError('Invalid Slayer tier')
  return 'N/A'
end
end
end


function p.getSlayerTier(name)
--
  for i, tier in Shared.skpairs(ConstantData.Slayer.Tiers) do
--- End of slayer functions
    if tier.display == name then
 
      local result = Shared.clone(tier)
--Turns a modifier name like 'increasedHPRegenFlat' into several pieces of data:
      result.id = i - 1
--Base Name, Description, IsNegative, and modifyValue
      return result
--ex. "HPRegenFlat", "+${value} Flat Hitpoints Regeneration", false, "multiplyByNumberMultiplier"
    end
function p.getModifierDetails(modifierName)
  end
local baseName = modifierName
end
local modifier = GameData.rawData.modifierData[modifierName]
 
if modifier == nil then
return nil
end


function p.getSlayerTierByID(tierID)
if Shared.startsWith(modifierName, "increased") or Shared.startsWith(modifierName, "decreased") then
  if ConstantData.Slayer.Tiers[tierID + 1] == nil then
baseName = string.sub(modifierName, 10)
    return nil
end
  end


  local result = Shared.clone(ConstantData.Slayer.Tiers[tierID + 1])
return baseName, modifier.description, modifier.isNegative, modifier.modifyValue
  result.id = tierID
  return result
end
end


function p._getModifierText(modifier, value, doColor)
function p._getModifierText(modifier, value, doColor)
  if doColor == nil then doColor = true end
if doColor == nil then doColor = true end
  local template = modifierTypes[modifier]
local modName, modText, isNegative, modifyValue = p.getModifierDetails(modifier)


  if template == nil then
if modName == nil then
    return 'ERROR: Invalid modifier type [[Category:Pages with script errors]]'
return Shared.printError('Invalid modifier type for "' .. modifier .. '"')
  end
end


  local result = template.text
if modifyValue ~= nil and string.match(modifyValue, 'ToolLevels') then
modifyValue = 'ArchaeologyToolLevels'
end


  if type(value) == 'table' then
local formatModValue = function(value, rule)
    if Shared.tableCount(value) > 0 and type(value[1]) == 'table' then
local ruleFunctions = {
      --Potentially return multiple rows if necessay
['value'] = function(val) return val end,
      local resultArray = {}
['multiplyByNumberMultiplier'] = function(val) return val * 10 end,
      for i, subVal in Shared.skpairs(value) do
['divideByNumberMultiplier'] = function(val) return val / 10 end,
        table.insert(resultArray, p._getModifierText(modifier, subVal, doColor))
['milliToSeconds'] = function(val) return val / 1000 end,
      end
['(value)=>value*100'] = function(val) return val * 100 end,
      return table.concat(resultArray, '<br/>')
['(value)=>100+value'] = function(val) return val + 100 end,
    else
['(value)=>value+1'] = function(val) return val + 1 end,
      if value[1] ~= nil then
['(value)=>Math.pow(2,value)'] = function(val) return 2^val end,
        local skillName = p.getSkillName(value[1])
["(value)=>{if(value>=2){return getLangString('ALLOW_UNHOLY_PRAYERS');}\nelse if(value>=1){return getLangString('ALLOW_UNHOLY_PRAYERS_WITH_EQUIPMENT');}\nelse{return 'Invalid modifier value.';}}"] = function(val) return 'Allows for Unholy Prayers to be used' end,
        if skillName ~= nil then
['ArchaeologyToolLevels'] = function(val)
          result = string.gsub(result, '{SV0}', p.getSkillName(value[1]))
local toolLevel = '+' .. val
        end
if string.match(modName, 'Sieve') then
      end
toolLevel = toolLevel ..  ' level of the Sieve Tool in Archaeology'
      if value[2] ~= nil then
elseif string.match(modName, 'Trowel') then
        result = string.gsub(result, '{V1}', value[2])
toolLevel = toolLevel ..  ' level of the Trowel Tool in Archaeology'
        result = string.gsub(result, '{VMS1}', value[2] / 1000)
elseif string.match(modName, 'Brush') then
      end
toolLevel = toolLevel ..  ' level of the Brush Tool in Archaeology'
    end
elseif string.match(modName, 'Shovel') then
  else
toolLevel = toolLevel .. ' level of the Shovel Tool in Archaeology'
    if string.find(result, '{IV}', 1, true) ~= nil and tonumber(value) ~= nil then
end
      local item = ItemData.Items[tonumber(value) + 1]
if val > 1 then
      if item ~= nil then
return string.gsub(toolLevel, 'level', 'levels')
        result = string.gsub(result, '{IV}', item.name)
else
      end
return toolLevel
    end
end
    result = string.gsub(result, '{V}', value)
end,
    result = string.gsub(result, '{VMS}', value / 1000)
['(value)=>game.golbinRaid.startingWeapons[value].name'] = function(val)
    result = string.gsub(result, '{VX}', value * 10)
-- For golbin raid starting weapons
  end
local startingWeapons = { 'melvorD:Bronze_Scimitar', 'melvorD:Adamant_Scimitar' }
local itemID = startingWeapons[val + 1]
local item = GameData.getEntityByID('items', itemID)
if item ~= nil then
return item.name
else
return 'Unknown'
end
end
}
local ruleFunc = ruleFunctions[modifyValue] or ruleFunctions['value']


  if doColor then
if type(value) == 'table' then
    if template.isNegative ~= nil and template.isNegative then
-- If table is a pair of values then format both & add a separator
      result = '<span style="color:red">'..result..'</span>'
return ruleFunc(value[1]) .. '-' .. ruleFunc(value[2])
    else
else
      result = '<span style="color:green">'..result..'</span>'
return ruleFunc(value)
    end
end
  end
end


  return result
local valueArray, resultArray = nil, {}
if type(value) ~= 'table' then
valueArray = {value}
else
valueArray = value
end
 
for i, subVal in ipairs(valueArray) do
local resultText = modText
-- A few modifiers don't have official descriptions; Fallback to manual ones instead
if string.match(resultText, 'UNDEFINED TRANSLATION') then
resultText = modifierTypes[modName].text
end
local modMagnitude = nil
if type(subVal) == 'table' and subVal.skillID ~= nil then
-- Modifier value is skill specific
modMagnitude = subVal.value
local skillName = p.getSkillName(subVal.skillID)
if skillName ~= nil then
resultText = string.gsub(resultText, '${skillName}', skillName)
end
else
-- Modifier value is the magnitude only
modMagnitude = subVal
end
 
resultText = string.gsub(resultText, '${value}', function(rule) return (formatModValue(modMagnitude, rule) or '') end)
 
if doColor then
local colorCode = (isNegative ~= nil and isNegative and 'color:red' or 'color:green')
resultText = '<span style="' .. colorCode .. '">' .. resultText .. '</span>'
end
table.insert(resultArray, resultText)
end
 
return table.concat(resultArray, '<br/>')
end
end


function p.getModifierText(frame)
function p.getModifierText(frame)
  local modifier = frame.args ~= nil and frame.args[1] or frame[1]
local modifier = frame.args ~= nil and frame.args[1] or frame[1]
  local value = frame.args ~= nil and frame.args[2] or frame[2]
local value = frame.args ~= nil and frame.args[2] or frame[2]
  local skill = frame.args ~= nil and frame.args.skill or frame.skill
local skill = frame.args ~= nil and frame.args.skill or frame.skill
  local doColor = frame.args ~= nil and frame.args[3] or frame[3]
local doColor = frame.args ~= nil and frame.args[3] or frame[3]


  if doColor ~= nil then
if doColor ~= nil then
    doColor = string.upper(doColor) ~= 'FALSE'
doColor = string.upper(doColor) ~= 'FALSE'
  end
end


  if skill ~= nil and skill ~= '' then
if skill ~= nil and skill ~= '' then
    value = {p.getSkillID(skill), value}
value = { { ["skillID"] = p.getSkillID(skill), ["value"] = value } }
  end
end
 
return p._getModifierText(modifier, value, doColor)
end
 
function p.getModifiersText(modifiers, doColor, inline, maxVisible)
if inline == nil then inline = false end
if type(maxVisible) ~= 'number' then maxVisible = nil end
if modifiers == nil or Shared.tableIsEmpty(modifiers) then
return ''
end
 
local modArray = { ["visible"] = {}, ["overflow"] = {} }
local modCount = { ["visible"] = 0, ["overflow"] = 0 }
local insertKey = 'visible'
for bonus, value in pairs(modifiers) do
-- If there is a single by skill modifier with multiple values, this will
-- appear as multiple rows. Split these so the number of visible lines is
-- always accurate
local valueArray = nil
if type(value) == 'table' then
valueArray = value
else
valueArray = {value}
end
for i, subVal in ipairs(valueArray) do
if type(subVal) == 'table' and subVal.skillID ~= nil then
subVal = {subVal}
end
if maxVisible ~= nil and not inline and insertKey == 'visible' and modCount[insertKey] >= maxVisible then
insertKey = 'overflow'
end
table.insert(modArray[insertKey], p._getModifierText(bonus, subVal, doColor))
modCount[insertKey] = modCount[insertKey] + 1
end
end
if inline then
return table.concat(modArray['visible'], ' and ')
else
if modCount['overflow'] == 1 then
table.insert(modArray['visible'], modArray['overflow'][1])
end
local overflowText = ''
if modCount['overflow'] > 1 then
-- Number of other modifiers has exceeded the specified maximum
overflowText = table.concat({
'<br/><span class="mw-collapsible mw-collapsed" ',
'data-expandtext="Show ' .. Shared.formatnum(modCount['overflow']) .. ' more modifiers" ',
'data-collapsetext="Hide">',
table.concat(modArray['overflow'], '<br/>'),
'</span>'
})
end
return table.concat(modArray['visible'], '<br/>') .. overflowText
end
end
 
function p.getModifierSkills(modifiers)
local skillArray = {}
 
for modifier, value in pairs(modifiers) do
if type(value) == 'table' then
for i, subVal in ipairs(value) do
if type(subVal) == 'table' and subVal.skillID ~= nil then
local skillName = p.getSkillName(subVal.skillID)
if not Shared.contains(skillArray, skillName) then
table.insert(skillArray, skillName)
end
end
end
end
 
local baseName = p.getModifierDetails(modifier)
if baseName == nil then
return { Shared.printError('Modifier "' .. modifier .. '" is invalid') }
end
 
if modifierTypes[baseName].skills ~= nil then
for i, skillName in Shared.skpairs(modifierTypes[baseName].skills) do
if not Shared.contains(skillArray, skillName) then
table.insert(skillArray, skillName)
end
end
end
end
 
return skillArray
end
 
-- Returns a modifiers table indicating modifiersNew less modifiersOld
-- The returned table can be passed to getModifiersText to obtain the
-- result in a human readable format
function p.getModifiersDifference(modifiersOld, modifiersNew)
local modHasPrefix = {}
local modDiff, modDiffBase = {}, {}
local allMods = {
{ ["mods"] = (modifiersNew or {}), ["mult"] = 1 },
{ ["mods"] = (modifiersOld or {}), ["mult"] = -1 }
}
 
-- Generate modifiers table containing only variances
-- Create modDiffBase with mod base names (less 'increased'/'decreased' prefixes),
for i, modDef in ipairs(allMods) do
for modName, value in pairs(modDef.mods) do
local modBaseName, modIsIncrease = modName, true
if Shared.startsWith(modName, "increased") or Shared.startsWith(modName, "decreased") then
modBaseName = string.sub(modName, 10)
modIsIncrease = Shared.startsWith(modName, "increased")
modHasPrefix[modBaseName] = true
end
local modMult = (modIsIncrease and 1 or -1) * modDef.mult
 
if type(value) == 'table' then
-- Skill specific modifier
if modDiffBase[modBaseName] == nil then
modDiffBase[modBaseName] = {}
end
for j, subVal in ipairs(value) do
if type(subVal) == 'table' and subVal.skillID ~= nil then
modDiffBase[modBaseName][subVal.skillID] = (modDiffBase[modBaseName][subVal.skillID] or 0) + subVal.value * modMult
end
end
else
modDiffBase[modBaseName] = (modDiffBase[modBaseName] or 0) + value * modMult
end
end
end
 
-- Transform modDiffBase into modDiff with the appropriate mod name within the return value
for modBaseName, value in pairs(modDiffBase) do
if type(value) == 'table' then
-- Skill specific modifier
for skillID, subVal in pairs(value) do
if subVal ~= 0 then
local modName = nil
if modHasPrefix[modBaseName] then
modName = (subVal < 0 and 'decreased' or 'increased') .. modBaseName
else
modName = modBaseName
end
if modDiff[modName] == nil then
modDiff[modName] = {}
end
table.insert(modDiff[modName], { ["skillID"] = skillID, ["value"] = math.abs(subVal) })
end
end
elseif value ~= 0 then
local modName = nil
if modHasPrefix[modBaseName] then
modName = (value < 0 and 'decreased' or 'increased') .. modBaseName
else
modName = modBaseName
end
modDiff[modName] = (modDiff[modName] or 0) + math.abs(value)
if GameData.rawData.modifierData[modName] == nil then
modDiff[modName] = nil
end
end
end
 
return modDiff
end


  return p._getModifierText(modifier, value, doColor)
-- From game version 1.1 onwards, some entities have custom descriptions, while
-- many are generated based on the modifiers associated to that entity. This
-- function handles that logic given a custom description (may be nil) and
-- a modifiers object
function p.getDescription(customDescription, modifiers)
if customDescription ~= nil then
return customDescription
else
return p.getModifiersText(modifiers, false)
end
end
end


return p
return p
457

edits