Module:Constants: Difference between revisions

From Melvor Idle
(Attempt to rewrite getModifierText so that we only need one row for pairs, not two. We'll see what breaks.)
No edit summary
 
(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')
local Modifiers = require('Module:Modifiers')


--Just hardcoding these because I guess that's where we're at
function p.getTriangleAttribute(attribute, attackerStyle, targetStyle, modeName)
local modifierTypes = {
if type(attribute) ~= 'string' then
["MeleeStrengthBonus"] = { text = "{V}% Melee Strength Bonus" },
error("Parameter 'attribute' must be a string", 2)
  ["DamageToDungeonMonsters"] = { text = "{V}% Damage To Dungeon Monsters" },
elseif type(attackerStyle) ~= 'string' then
  ["GlobalMasteryXP"] = { text = "{V}% Global Mastery XP" },
error("Parameter 'attackerStyle' must be a string", 2)
  ["ChanceRandomPotionHerblore"] = { text = "{V}% chance to gain a second potion of a random tier" },
elseif type(targetStyle) ~= 'string' then
  ["FlatPrayerCostReduction"] = { text = "{V} Prayer Point Cost for Prayers", inverseSign = True },
error("Parameter 'targetStyle' must be a string", 2)
  ["MinEarthSpellDmg"] = { text = "{VX} Min Earth Spell Dmg" },
elseif type(modeName) ~= 'string' then
  ["SlayerTaskLength"] = { text = "{V}% Slayer Task Length/Qty" },
error("Parameter 'modeName' must be a string", 2)
  ["ChanceToDoubleLootCombat"] = { text = "{V}% Chance To Double Loot in Combat" },
end
  ["GPFromAgility"] = { text = "{V}% GP From Agility" },
  ["SkillXP"] = { text = "{V1}% {SV0} Skill XP" },
local mode = GameData.getEntityByName('gamemodes', modeName)
  ["MiningNodeHP"] = { text = "{V} Mining Node HP" },
if mode == nil then
  ["StaminaPerObstacle"] = { text = "{V} Stamina per Agility Obstacle Completion" },
error("Invalid gamemode '" .. modeName .. "'", 2)
  ["ChanceToDoubleItems"] = { text = "{V} Chance To Double Items" },
end
  ["FarmingYield"] = { text = "{V}% Farming Yield" },
  ["GPFromMonstersFlat"] = { text = "{V} GP From Monsters" },
local attStyle, targStyle = string.lower(attackerStyle), string.lower(targetStyle)
  ["GlobalPreservationChance"] = { text = "{V}% Chance to Preserve Resources in Skills" },
local validStyles = { 'magic', 'melee', 'ranged' }
  ["RunePreservation"] = { text = "{V}% Rune Preservation" },
if not Shared.contains(validStyles, string.lower(attStyle)) then
  ["MaxHitpoints"] = { text = "{VX} Maximum Hitpoints" },
error("Invalid value for parameter 'attackerStyle'", 2)
  ["ChanceToDoubleItemsSkill"] = { text = "{V1}% Chance to Double Items in {SV0}" },
elseif not Shared.contains(validStyles, string.lower(targStyle)) then
  ["autoSlayerUnlocked"] = { text = "{V} Auto Slayer Unlocked" },
error("Invalid value for parameter 'targetStyle'", 2)
  ["HitpointRegeneration"] = { text = "{V}% Hitpoint Regeneration" },
end
  ["SlayerXP"] = { text = "{V}% Slayer XP" },
  ["PotionChargesFlat"] = { text = "{V} Charges per Potion" },
local combatTriangle = GameData.getEntityByID('combatTriangles', mode.combatTriangle)
  ["SkillInterval"] = { text = "{VMS1}s {SV0} Interval", isIncreaseNegative = True },
if combatTriangle == nil then
  ["BankSpace"] = { text = "{V} Bank Space" },
error("No such combat triangle: " .. mode.combatTriangle)
  ["MinHitBasedOnMaxHit"] = { text = "{V}% of Maximum Hit added to Minimum Hit" },
end
  ["DamageToSlayerTasks"] = { text = "{V}% Damage To Slayer Tasks" },
local attrData = combatTriangle[attribute]
  ["Lifesteal"] = { text = "{V}% Lifesteal" },
if attrData == nil then
  ["HPRegenFlat"] = { text = "{V} Flat HP Regen" },
error("No such attribute: " .. attribute)
  ["ChanceToDoubleOres"] = { text = "{V}% Chance to Double Ores in Mining" },
else
  ["MaxStamina"] = { text = "{V} Max Stamina" },
return attrData[attStyle][targStyle]
  ["MonsterRespawnTimer"] = { text = "{VMS}s Monster Respawn Timer", isIncreaseNegative = True },
end
  ["SkillPreservationChance"] = { text = "{V1}% Chance to Preserve Resources in {SV0}" },
end
  ["DamageToCombatAreaMonsters"] = { text = "{V}% Damage To Combat Area Monsters" },
  ["TreeCutLimit"] = { text = "{V} Tree Cut Limit" },
  ["EquipmentSets"] = { text = "{V} Equipment Sets" },
  ["HiddenSkillLevel"] = { text = "{V1} Hidden {SV0} Level" },
  ["ChanceToPreservePrayerPoints"] = { text = "{V}% Chance To Preserve Prayer Points" },
  ["ReflectDamage"] = { text = "{V}% Reflect Damage" },
  ["MeleeEvasion"] = { text = "{V}% Melee Evasion" },
  ["DamageToSlayerAreaMonsters"] = { text = "{V}% Damage To Slayer Area Monsters" },
  ["GPFromMonsters"] = { text = "{V}% GP From Monsters" },
  ["MagicEvasion"] = { text = "{V}% Magic Evasion" },
  ["PlayerAttackSpeedPercent"] = { text = "{V}% Player Attack Speed", isIncreaseNegative = True },
  ["PreservationChance"] = { text = "{V}% Chance to Preserve Resources" },
  ["DamageReduction"] = { text = "{V}% Damage Reduction" },
  ["MinWaterSpellDmg"] = { text = "{VX} Min Water Spell Dmg" },
  ["DamageToAllMonsters"] = { text = "{V}% Damage To All Monsters" },
  ["golbinRaidIncreasedStartingRuneCount"] = { text = "{V} to starting Elemental Rune count" },
  ["FoodHealingValue"] = { text = "{V}% Food Healing Value" },
  ["MinFireSpellDmg"] = { text = "{VX} Min Fire Spell Dmg" },
  ["SlayerCoins"] = { text = "{V}% Slayer Coins" },
  ["GPFromThievingFlat"] = { text = "{V} GP From Thieving" },
  ["GlobalAccuracy"] = { text = "{V}% Global Accuracy" },
  ["ChanceToDoubleLootThieving"] = { text = "{V}% Chance To Double Loot in Thieving" },
  ["SlayerAreaEffectNegationFlat"] = { text = "{V}% Flat Slayer Area Effect Negation" },
  ["MagicAccuracyBonus"] = { text = "{V}% Magic Accuracy Bonus" },
  ["SkillIntervalPercent"] = { text = "{V1}% {SV0} Interval", isIncreaseNegative = True },
  ["GlobalSkillXP"] = { text = "{V}% Global Skill XP" },
  ["MeleeAccuracyBonus"] = { text = "{V}% Melee Accuracy Bonus" },
  ["DamageToBosses"] = { text = "{V}% Damage To Bosses" },
  ["ChanceToPreservePotionCharge"] = { text = "{V}% Chance To Preserve Potion Charge" },
  ["MaxHitPercent"] = { text = "{V}% Max Hit" },
  ["AltMagicSkillXP"] = { text = "{V}% Alt. Magic Skill XP" },
  ["StaminaPreservationChance"] = { text = "{V}% Chance to Preserve Stamina" },
  ["MinAirSpellDmg"] = { text = "{VX} Min Air Spell Dmg" },
  ["AutoEatEfficiency"] = { text = "{V}% Auto Eat Efficiency" },
  ["GPFromThieving"] = { text = "{V}% GP From Thieving" },
  ["ChanceToDoubleItemsGlobal"] = { text = "{V}% Chance to Double Items Globally" },
  ["GPGlobal"] = { text = "{V}% GP from all sources (Except Item Selling)" },
  ["RangedAccuracyBonus"] = { text = "{V} Ranged Accuracy Bonus" },
  ["AutoEatThreshold"] = { text = "{V}% Auto Eat Threshold" },
  ["PlayerAttackSpeed"] = { text = "{VMS}s Player Attack Speed", isIncreaseNegative = True },
  ["freeBonfires"] = { text = "+ Automatically relight bonfires for free" },
  ["AutoEatHPLimit"] = { text = "{V}% Auto Eat HP Limit" },
  ["BankSpaceShop"] = { text = "{V} Bank Space from Shop" },
  ["BirdNestDropRate"] = { text = "{V}% Bird Nest drop rate" },
  ["RangedEvasion"] = { text = "{V}% Ranged Evasion" },
  ["ChanceDoubleHarvest"] = { text = "{V}% chance for double harvest" },
  ["golbinRaidStartingWeapon"] = { text = "Start the Golbin Raid with an {IV}" },
  ["AttackRolls"] = { text = "+Lucky Hit Chance (Roll twice, take the better result)" },
  ["AmmoPreservation"] = { text = "{V}% Ammo Preservation" },
  ["RangedStrengthBonus"] = { text = "{V}% Ranged Strength Bonus" },
  ["MagicDamageBonus"] = { text = "{V}% Magic Damage Bonus" },
  ["MasteryXP"] = { text = "{V1}% {SV0} Mastery XP" },
  ["dungeonEquipmentSwapping"] = { text = "{V} Dungeon Equipment Swapping" },
  ["SeeingGoldChance"] = { text = "{V}% chance for Silver Ore to also produce a Gold Bar when smithed" },
  ["ElementalRuneGain"] = { text = "{V} runes received when generating random elemental runes" },
  ["StaminaCost"] = { text = "{V} Stamina Cost per action", isIncreaseNegative = True },
  ["GPFromSales"] = { text = "{V}% GP From Sales" },
  ["MaxHitFlat"] = { text = "{VX} Max Hit" },
  ["ChanceNoDamageMining"] = { text = "{V}% chance to do zero damage to Ores and Rune Essence" },
  ["ChanceForElementalRune"] = { text = "{V}% chance to receive random elemental runes per Runecraft" },
}


--Difficulties are hard coded which is dumb but means hardcoding them here too
function p.getTriangleDamageModifier(attackerStyle, targetStyle, mode)
local Difficulties = {
return p.getTriangleAttribute('damageModifier', attackerStyle, targetStyle, mode)
    [0] = 'Very Easy',
end
    [1] = 'Easy',
 
    [2] = 'Medium',
--Syntax is like p.getTriangleDRModifier('Melee', 'Ranged', 'Normal')
    [3] = 'Hard',
--Returns a multiplier that can be multiplied with base DR to get the post-Triangle result
    [4] = 'Very Hard',
function p.getTriangleDRModifier(attackerStyle, targetStyle, mode)
    [5] = 'Elite',
return p.getTriangleAttribute('reductionModifier', attackerStyle, targetStyle, mode)
    [6] = 'Insane'}
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
 
function p.getSkillIDText(frame)
local skillName = frame.args ~= nil and frame.args[1] or frame[1]
local id = p.getSkillID(skillName)
if id == nil or id == '' then
return "Unknown"
else
return id
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.emptyName
    end
end
  end
  return 'Invalid'
end
end


function p.getEquipmentSlotID(name)
function p.getEquipmentSlotID(name)
  return ConstantData.equipmentSlot[name]
local slotData = GameData.getEntityByProperty('equipmentSlots', 'emptyName', 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
end


function p.getSlayerTierName(tier)
 
  for name, num in Shared.skpairs(ConstantData.slayerTier) do
--- Slayer functions
    if num == tier then
--
      return name
function p.getSlayerTierByID(tierID)
    end
if type(tierID) ~= 'number' then
  end
return nil
  return "ERROR: Invalid Slayer tier[[Category:Pages with script errors]]"
else
return GameData.rawData.slayerTaskCategories[tierID + 1]
end
end
end


function p.getSlayerTierNameByLevel(lvl)
function p.getSlayerTier(name)
  for i, tier in Shared.skpairs(ConstantData.Slayer.Tiers) do
return GameData.getEntityByProperty('slayerTiers', 'display', name)
    if tier.minLevel <= lvl and (tier.maxLevel >= lvl or tier.maxLevel == -1) then
      return tier.display
    end
  end
  return 'N/A'
end
end


function p.getSlayerTier(name)
function p.getSlayerTierByLevel(level)
  for i, tier in Shared.skpairs(ConstantData.Slayer.Tiers) do
if type(level) ~= 'number' or level < 1 then
    if tier.display == name then
return Shared.printError('Invalid Slayer level')
      local result = Shared.clone(tier)
end
      result.id = i - 1
 
      return result
for i, tier in ipairs(GameData.rawData.slayerTaskCategories) do
    end
if tier.type == 'CombatLevel' and tier.minLevel <= level and (tier.maxLevel == nil or tier.maxLevel >= level) then
  end
return tier
end
end
end
end


function p.getSlayerTierByID(tierID)
--
  if ConstantData.Slayer.Tiers[tierID + 1] == nil then
-- the following functions just return subsets of the slayer functions above
    return nil
--
  end


  local result = Shared.clone(ConstantData.Slayer.Tiers[tierID + 1])
function p.getSlayerTierName(tierID)
  result.id = tierID
if type(tierID) == 'number' then
  return result
local tier = p.getSlayerTierByID(tierID)
if tier ~= nil then
return tier.display
end
end
return Shared.printError('Invalid Slayer tier')
end
end


--Turns a modifier name like 'increasedMeleeAccuracyBonus' into several pieces of data:
function p.getSlayerTierNameByLevel(lvl)
--Base Name, Text, Sign, and IsNegative
local tier = p.getSlayerTierByLevel(lvl)
--ex. "MeleeAccuracyBonus", "+{V}% Melee Accuracy", "+", false
if type(tier) == 'table' then
function p.getModifierDetails(modifierName)
return tier.display
  local baseName = modifierName
else
  local isIncrease = true
return Shared.printError('Invalid Slayer tier')
  local isNegative = false
end
end


  if Shared.startsWith(modifierName, "increased") or Shared.startsWith(modifierName, "decreased") then
--
    baseName = string.sub(modifierName, 10)
--- End of slayer functions
    isIncrease = Shared.startsWith(modifierName, "increased")
  end


  local modifier = modifierTypes[baseName]
--Turns a modifier name like 'increasedHPRegenFlat' into several pieces of data:
  if modifier == nil then
--Base Name, Description, IsNegative, and modifyValue
    mw.log(baseName)
--ex. "HPRegenFlat", "+${value} Flat Hitpoints Regeneration", false, "multiplyByNumberMultiplier"
    return nil
function p.getModifierDetails(modifierName)
  end
-- Unsupported
error('Function is no longer supported', 2)
--[==[
local baseName = modifierName
local modifier = GameData.rawData.modifierData[modifierName]


  local isPositive = isIncrease
if modifier == nil then
  if modifier.isIncreaseNegative then
return nil
    isPositive = not isPositive
end
  end


  local sign = "+"
if Shared.startsWith(modifierName, "increased") or Shared.startsWith(modifierName, "decreased") then
  if (not isIncrease and not modifier.inverseSign) or (isIncrease and modifier.inverseSign) then
baseName = string.sub(modifierName, 10)
    sign = "-"
end
  end


  return baseName, modifier.text, sign, not isPositive
return baseName, modifier.description, modifier.isNegative, modifier.modifyValue
--]==]
end
end


function p._getModifierText(modifier, value, doColor)
function p._getModifierText(modifier, value, doColor)
  if doColor == nil then doColor = true end
return Modifiers.getModifierText(modifier, value, doColor)
  local modName, modText, sign, isNegative = p.getModifierDetails(modifier)
end


  if modName == nil then
function p.getModifierText(frame)
    return 'ERROR: Invalid modifier type [[Category:Pages with script errors]]'
local modifier = frame.args ~= nil and frame.args[1] or frame[1]
  end
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 doColor = frame.args ~= nil and frame.args[3] or frame[3]


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


  if type(value) == 'table' then
if skill ~= nil and skill ~= '' then
    if Shared.tableCount(value) > 0 and type(value[1]) == 'table' then
value = { { ["skillID"] = p.getSkillID(skill), ["value"] = value } }
      --Potentially return multiple rows if necessary
end
      local resultArray = {}
      for i, subVal in Shared.skpairs(value) do
        table.insert(resultArray, p._getModifierText(modifier, subVal, doColor))
      end
      return table.concat(resultArray, '<br/>')
    else
      if value[1] ~= nil then
        local skillName = p.getSkillName(value[1])
        if skillName ~= nil then
          result = string.gsub(result, '{SV0}', p.getSkillName(value[1]))
        end
      end
      if value[2] ~= nil then
        result = string.gsub(result, '{V1}', sign..value[2])
        result = string.gsub(result, '{VMS1}', sign..(value[2] / 1000))
      end
    end
  else
    if string.find(result, '{IV}', 1, true) ~= nil and tonumber(value) ~= nil then
      local item = ItemData.Items[tonumber(value) + 1]
      if item ~= nil then
        result = string.gsub(result, '{IV}', item.name)
      end
    end
    result = string.gsub(result, '{V}', sign..value)
    result = string.gsub(result, '{VMS}', sign..(value / 1000))
    result = string.gsub(result, '{VX}', sign..(value * 10))
  end


  if doColor then
return p._getModifierText(modifier, value, doColor)
    if isNegative ~= nil and isNegative then
end
      result = '<span style="color:red">'..result..'</span>'
    else
      result = '<span style="color:green">'..result..'</span>'
    end
  end


  return result
function p.getModifiersText(modifiers, doColor, inline, maxVisible)
return Modifiers.getModifiersText(modifiers, doColor, inline, maxVisible)
end
end


function p.getModifierText(frame)
function p.getModifierSkills(modifiers)
  local modifier = frame.args ~= nil and frame.args[1] or frame[1]
return Modifiers.getModifierSkills(modifiers)
  local value = frame.args ~= nil and frame.args[2] or frame[2]
end
  local skill = frame.args ~= nil and frame.args.skill or frame.skill
 
  local doColor = frame.args ~= nil and frame.args[3] or frame[3]
-- 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


  if doColor ~= nil then
-- Transform modDiffBase into modDiff with the appropriate mod name within the return value
    doColor = string.upper(doColor) ~= 'FALSE'
for modBaseName, value in pairs(modDiffBase) do
  end
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


  if skill ~= nil and skill ~= '' then
return modDiff
    value = {p.getSkillID(skill), value}
end
  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

Latest revision as of 22:37, 22 June 2024

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

local p = {}

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

function p.getTriangleAttribute(attribute, attackerStyle, targetStyle, modeName)
	if type(attribute) ~= 'string' then
		error("Parameter 'attribute' must be a string", 2)
	elseif type(attackerStyle) ~= 'string' then
		error("Parameter 'attackerStyle' must be a string", 2)
	elseif type(targetStyle) ~= 'string' then
		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)
	return GameData.rawData.combatAreaDifficulties[difficulty + 1]
end

function p.getSkillName(skillID)
	local skill = GameData.getSkillData(skillID)
	if skill ~= nil then
		return skill.name
	end
end

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

function p.getSkillIDText(frame)
	local skillName = frame.args ~= nil and frame.args[1] or frame[1]
	local id = p.getSkillID(skillName)
	
	if id == nil or id == '' then
		return "Unknown"
	else
		return id
	end
end

function p.getEquipmentSlotName(id)
	local slotData = GameData.getEntityByID('equipmentSlots', id)
	if slotData ~= nil then
		return slotData.emptyName
	end
end

function p.getEquipmentSlotID(name)
	local slotData = GameData.getEntityByProperty('equipmentSlots', 'emptyName', name)
	if slotData ~= nil then
		return slotData.id
	end
end

function p.getCombatStyleName(styleNum)
	if type(styleNum) == 'number' then
		local styleName = GameData.rawData.attackTypes[styleNum]
		if styleName ~= nil then
			return Shared.titleCase(styleName)
		end
	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.slayerTaskCategories[tierID + 1]
	end
end

function p.getSlayerTier(name)
	return GameData.getEntityByProperty('slayerTiers', 'display', name)
end

function p.getSlayerTierByLevel(level)
	if type(level) ~= 'number' or level < 1 then
		return Shared.printError('Invalid Slayer level')
	end

	for i, tier in ipairs(GameData.rawData.slayerTaskCategories) do
		if tier.type == 'CombatLevel' and 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

function p.getSlayerTierNameByLevel(lvl)
	local tier = p.getSlayerTierByLevel(lvl)
	if type(tier) == 'table' then
		return tier.display
	else
		return Shared.printError('Invalid Slayer tier')
	end
end

--
--- End of slayer functions

--Turns a modifier name like 'increasedHPRegenFlat' into several pieces of data:
--Base Name, Description, IsNegative, and modifyValue
--ex. "HPRegenFlat", "+${value} Flat Hitpoints Regeneration", false, "multiplyByNumberMultiplier"
function p.getModifierDetails(modifierName)
	-- Unsupported
	error('Function is no longer supported', 2)
	--[==[
	local baseName = modifierName
	local modifier = GameData.rawData.modifierData[modifierName]

	if modifier == nil then
		return nil
	end

	if Shared.startsWith(modifierName, "increased") or Shared.startsWith(modifierName, "decreased") then
		baseName = string.sub(modifierName, 10)
	end

	return baseName, modifier.description, modifier.isNegative, modifier.modifyValue
	--]==]
end

function p._getModifierText(modifier, value, doColor)
	return Modifiers.getModifierText(modifier, value, doColor)
end

function p.getModifierText(frame)
	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 skill = frame.args ~= nil and frame.args.skill or frame.skill
	local doColor = frame.args ~= nil and frame.args[3] or frame[3]

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

	if skill ~= nil and skill ~= '' then
		value = { { ["skillID"] = p.getSkillID(skill), ["value"] = value } }
	end

	return p._getModifierText(modifier, value, doColor)
end

function p.getModifiersText(modifiers, doColor, inline, maxVisible)
	return Modifiers.getModifiersText(modifiers, doColor, inline, maxVisible)
end

function p.getModifierSkills(modifiers)
	return Modifiers.getModifierSkills(modifiers)
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

-- 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

return p