Module:Constants: Difference between revisions

From Melvor Idle
(Fixed True -> true)
No edit summary
 
(130 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