1,162
edits
Falterfire (talk | contribs) (returning Modifier as another part of getModifierDetails) |
(Updated Arch Tool Level display, Fixed error on nil modifiers in getModifiersDifference) |
||
(102 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
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 = { | ||
["MeleeStrengthBonus"] = { text = "{V}% Melee Strength Bonus from Equipment", skills = {'Combat'} }, | |||
["DamageToDungeonMonsters"] = { text = "{V}% Damage To Dungeon Monsters", skills = {'Combat'} }, | |||
["GlobalMasteryXP"] = { text = "{V}% Global Mastery XP", skills = {'Woodcutting', 'Fishing', 'Firemaking', 'Cooking', 'Mining', 'Smithing', 'Thieving', 'Farming', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Agility', 'Summoning', 'Astrology', 'Archaeology'} }, | |||
["ChanceRandomPotionHerblore"] = { text = "{V}% chance to gain a second potion of a random tier", skills = {'Herblore'} }, | |||
["FlatPrayerCostReduction"] = { text = "{V} Prayer Point Cost for Prayers (Prayer Point cost cannot go below 1)", inverseSign = true, skills = {'Prayer'} }, | |||
["MinEarthSpellDmg"] = { text = "{VX} Min Earth Spell Dmg", skills = {'Magic'} }, | |||
["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'} }, | |||
[" | |||
} | } | ||
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( | function p.getTriangleDamageModifier(attackerStyle, targetStyle, mode) | ||
return p.getTriangleAttribute('damageModifier', attackerStyle, targetStyle, mode) | |||
end | end | ||
--Syntax is like p.getTriangleDRModifier('Melee', 'Ranged', 'Normal') | --Syntax is like p.getTriangleDRModifier('Melee', 'Ranged', 'Normal') | ||
--Returns a multiplier that can be multiplied with base DR to get the post-Triangle result | --Returns a multiplier that can be multiplied with base DR to get the post-Triangle result | ||
function p.getTriangleDRModifier( | function p.getTriangleDRModifier(attackerStyle, targetStyle, mode) | ||
return p.getTriangleAttribute('reductionModifier', attackerStyle, targetStyle, mode) | |||
end | end | ||
function p.getDifficultyString(difficulty) | function p.getDifficultyString(difficulty) | ||
return GameData.rawData.combatAreaDifficulties[difficulty + 1] | |||
end | end | ||
function p.getSkillName(skillID) | function p.getSkillName(skillID) | ||
return | local skill = GameData.getSkillData(skillID) | ||
if skill ~= nil then | |||
return skill.name | |||
end | |||
end | end | ||
function p.getSkillID(skillName) | function p.getSkillID(skillName) | ||
return | 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) | ||
local slotData = GameData.getEntityByID('equipmentSlots', id) | |||
if slotData ~= nil then | |||
return slotData.name | |||
end | |||
end | end | ||
function p.getEquipmentSlotID(name) | function p.getEquipmentSlotID(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) | ||
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 | end | ||
function p. | |||
--- Slayer functions | |||
-- | |||
function p.getSlayerTierByID(tierID) | |||
if type(tierID) ~= 'number' then | |||
return nil | |||
else | |||
return GameData.rawData.slayerTiers[tierID + 1] | |||
end | |||
end | end | ||
function p. | function p.getSlayerTier(name) | ||
return GameData.getEntityByProperty('slayerTiers', 'display', name) | |||
end | end | ||
function p. | 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.slayerTiers) do | |||
if tier.minLevel <= level and (tier.maxLevel == nil or tier.maxLevel >= level) then | |||
return tier | |||
end | |||
end | |||
end | end | ||
function p. | -- | ||
-- 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 | ||
--Turns a modifier name like ' | -- | ||
--Base Name, | --- End of slayer functions | ||
--ex. " | |||
--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) | function p.getModifierDetails(modifierName) | ||
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) | |||
if doColor == nil then doColor = true end | |||
local modName, modText, isNegative, modifyValue = p.getModifierDetails(modifier) | |||
if modName == nil then | |||
return Shared.printError('Invalid modifier type for "' .. modifier .. '"') | |||
end | |||
if modifyValue ~= nil and string.match(modifyValue, 'ToolLevels') then | |||
modifyValue = 'ArchaeologyToolLevels' | |||
end | |||
local formatModValue = function(value, rule) | |||
end | local ruleFunctions = { | ||
['value'] = function(val) return val end, | |||
['multiplyByNumberMultiplier'] = function(val) return val * 10 end, | |||
['divideByNumberMultiplier'] = function(val) return val / 10 end, | |||
['milliToSeconds'] = function(val) return val / 1000 end, | |||
['(value)=>value*100'] = function(val) return val * 100 end, | |||
['(value)=>100+value'] = function(val) return val + 100 end, | |||
['(value)=>value+1'] = function(val) return val + 1 end, | |||
['(value)=>Math.pow(2,value)'] = function(val) return 2^val end, | |||
["(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, | |||
['ArchaeologyToolLevels'] = function(val) | |||
local toolLevel = '+' .. val | |||
if string.match(modName, 'Sieve') then | |||
toolLevel = toolLevel .. ' level of the Sieve Tool in Archaeology' | |||
elseif string.match(modName, 'Trowel') then | |||
toolLevel = toolLevel .. ' level of the Trowel Tool in Archaeology' | |||
elseif string.match(modName, 'Brush') then | |||
toolLevel = toolLevel .. ' level of the Brush Tool in Archaeology' | |||
elseif string.match(modName, 'Shovel') then | |||
toolLevel = toolLevel .. ' level of the Shovel Tool in Archaeology' | |||
end | |||
if val > 1 then | |||
return string.gsub(toolLevel, 'level', 'levels') | |||
else | |||
return toolLevel | |||
end | |||
end, | |||
['(value)=>game.golbinRaid.startingWeapons[value].name'] = function(val) | |||
-- For golbin raid starting weapons | |||
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 type(value) == 'table' then | |||
-- If table is a pair of values then format both & add a separator | |||
return ruleFunc(value[1]) .. '-' .. ruleFunc(value[2]) | |||
else | |||
return ruleFunc(value) | |||
end | |||
end | |||
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 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 | end | ||
function p.getModifiersText(modifiers, doColor) | 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 | end | ||
function p.getModifierSkills(modifiers) | 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 | |||
-- 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 |