17,105
edits
mNo edit summary |
(0.21 Special attack description handling) |
||
Line 71: | Line 71: | ||
return result | return result | ||
end | end | ||
function p.getItemByID(ID) | function p.getItemByID(ID) | ||
Line 304: | Line 306: | ||
result = result.."\r\n|-\r\n|'''Special Attack:'''" | result = result.."\r\n|-\r\n|'''Special Attack:'''" | ||
result = result..'\r\n* '..spAtt.chance..'% chance for '..spAtt.name..':' | result = result..'\r\n* '..spAtt.chance..'% chance for '..spAtt.name..':' | ||
result = result..'\r\n** '..spAtt | result = result..'\r\n** '..p._getSpecialAttackDescription(spAtt) | ||
end | end | ||
--For potions, show the number of charges | --For potions, show the number of charges | ||
Line 404: | Line 406: | ||
result = result..'\r\n|data-sort-value="'..spAttData.sortName..'"|'..table.concat(spAttData.Icons, '<br/>') | result = result..'\r\n|data-sort-value="'..spAttData.sortName..'"|'..table.concat(spAttData.Icons, '<br/>') | ||
result = result..'||'..spAtt.name..'||data-sort-value="'..spAtt.chance..'"|'..spAtt.chance..'%' | result = result..'||'..spAtt.name..'||data-sort-value="'..spAtt.chance..'"|'..spAtt.chance..'%' | ||
result = result..'||'..spAtt | result = result..'||'..p._getSpecialAttackDescription(spAtt) | ||
end | end | ||
result = result..'\r\n|}' | result = result..'\r\n|}' | ||
Line 410: | Line 412: | ||
return result | return result | ||
end | end | ||
local nouns = { | |||
["player"] = { | |||
["plain"] = "you", | |||
["possesive"] = "your", | |||
["pronoun"] = "you", | |||
["is"] = "are" | |||
}, | |||
["enemy"] = { | |||
["plain"] = "the enemy", | |||
["possesive"] = "the enemy's", | |||
["pronoun"] = "they", | |||
["is"] = "is" | |||
} | |||
} | |||
-- Generates a textual description from a special attack's damage attribute | |||
-- Similar to in game function getDamageDescription in attack.js | |||
function p._getDamageDescription(damageList, attName, tarName) | |||
local rollData = { | |||
["CurrentHP"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of ' .. name .. ' current hitpoints' end | |||
}, | |||
["MaxHP"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of ' .. name .. ' max hitpoints' end | |||
}, | |||
["DamageDealt"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of the damage dealt' end | |||
}, | |||
["MaxHit"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of ' .. name .. ' max hit' end | |||
}, | |||
["MinHit"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of ' .. name .. ' min hit' end | |||
}, | |||
["Fixed"] = { | |||
["formatPercent"] = function(value) return Shared.formatnum(math.floor(value * 10)) end, | |||
["formatName"] = function(name) return '' end | |||
}, | |||
["MagicScaling"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 0, 0) .. '%' end, | |||
["formatName"] = function(name) return ' of ' .. name .. ' current hitpoints' end | |||
}, | |||
["One"] = { | |||
["formatPercent"] = function(value) return '1' end, | |||
["formatName"] = function(name) return '' end | |||
}, | |||
["Rend"] = { | |||
["formatPercent"] = function(value) return Shared.round(value, 2, 0) .. '% if the target has full HP, otherwise 250%' end, | |||
["formatName"] = function(name) return ' of the damage dealt' end | |||
}, | |||
} | |||
local descPart = {} | |||
for i, damage in ipairs(damageList) do | |||
local name = 'Unknown' | |||
if damage.character == 'Attacker' then | |||
name = nouns[attName]['possesive'] | |||
elseif damage.character == 'Target' then | |||
name = nouns[tarName]['possesive'] | |||
end | |||
local maxData = rollData[damage['maxRoll']] | |||
local desc = '' | |||
if damage.roll then | |||
local minData = rollData[damage['minRoll']] | |||
if damage['maxRoll'] == damage['minRoll'] then | |||
-- Same damage type for max & min | |||
desc = minData.formatPercent(damage['minPercent']) .. '-' .. maxData.formatPercent(damage['maxPercent']) .. minData.formatName(name) | |||
elseif damage['maxRoll'] == 'MaxHit' and damage['minRoll'] == 'MinHit' and damage['maxPercent'] == damage['minPercent'] then | |||
desc = maxData.formatPercent(damage['maxPercent']) .. ' of ' .. name .. ' normal damage' | |||
else | |||
desc = minData.formatPercent(damage['minPercent']) .. minData.formatName(name) .. ' to ' .. maxData.formatPercent(damage['maxPercent']) .. maxData.formatName(name) | |||
end | |||
else | |||
desc = maxData.formatPercent(damage['maxPercent']) .. maxData.formatName(name) | |||
end | |||
table.insert(descPart, desc) | |||
end | |||
return Shared.joinList(descPart, ', ', ' and ') | |||
end | |||
function p._getEffectDescription(effectList, attName, tarName) | |||
local getAllModText = function(modifierList, doColor) | |||
local returnPart = {} | |||
for modName, val in pairs(modifierList) do | |||
table.insert(returnPart, Constants._getModifierText(modName, val, doColor)) | |||
end | |||
return Shared.joinList(returnPart, ', ', ' and ') | |||
end | |||
local descPart = {} | |||
for i, effect in ipairs(effectList) do | |||
local desc = '' | |||
if effect['type'] == 'DOT' then | |||
local damageDesc = p._getDamageDescription(effect['damage'], attName, tarName) | |||
local parts = nil | |||
if effect['subtype'] == 'Regen' then | |||
parts = {'give', 'heals', ''} | |||
else | |||
parts = {'inflict', 'deals', 'as damage '} | |||
end | |||
if effect['chance'] < 100 then | |||
desc = 'has a ' .. Shared.round(effect['chance'], 2, 0) .. '% chance to ' .. parts[1] .. ' ' .. effect['subtype'] .. ' ' | |||
else | |||
desc = parts[1] .. 's ' .. effect['subtype'] .. ' ' | |||
end | |||
desc = desc .. 'that ' .. parts[2] .. ' ' .. damageDesc .. ' ' .. parts[3] .. 'over ' .. Shared.round(effect['procs'] * effect['interval'] / 1000, 2, 0) .. 's' | |||
elseif effect['type'] == 'Reflexive' then | |||
local modDesc = getAllModText(effect.modifiers, false) | |||
desc = 'gives ' .. nouns[attName]['plain'] .. ' ' .. modDesc .. ' each time ' .. nouns[attName]['pronoun'] .. ' are hit for the duration of this attack (Stacks up to ' .. Shared.formatnum(math.floor(effect['maxStacks'])) .. ' times)' | |||
elseif effect['type'] == 'Stacking' then | |||
local modDesc = getAllModText(effect.modifiers, false) | |||
desc = 'applies +' .. Shared.formatnum(math.floor(effect['stacksToAdd'])) .. ' stack' .. (effect['stacksToAdd'] > 1 and 's' or '') .. ' of ' .. effect['name'] .. ' to ' .. nouns[tarName]['plain'] | |||
desc = desc .. ' (Max ' .. Shared.formatnum(math.floor(effect['maxStacks'])) .. ' stack' .. (effect['maxStacks'] > 1 and 's' or '') .. '). ' .. effect['name'] .. ' gives ' .. modDesc .. ' regardless of number of stacks. One stack is removed after each of the ' .. nouns[tarName]['possesive'] .. ' turns' | |||
elseif effect['type'] == 'Modifier' then | |||
local modDesc = getAllModText(effect.modifiers, false) | |||
local name = effect['character'] == 'Attacker' and attName or tarName | |||
local countName = effect['countsOn'] == 'Attacker' and attName or tarName | |||
desc = 'gives ' .. nouns[name]['plain'] .. ' ' .. modDesc | |||
if effect['maxStacks'] > 1 then | |||
desc = desc .. ' that stacks up to ' .. Shared.formatnum(math.floor(effect['maxStacks'])) .. ' times' | |||
elseif effect['turns'] == nil then | |||
-- Take a nil turns value to mean Infinity, as in JS JSON.Stringify() converts Infinity to null | |||
-- This assumption will potentially cause issues in the future | |||
desc = desc .. ' until the end of the fight' | |||
elseif effect['turns'] > 0 then | |||
desc = desc .. ' for ' .. Shared.formatnum(math.floor(effect['turns'])) .. ' of ' .. nouns[countName]['possesive'] .. ' turn' .. (effect['turns'] > 1 and 's' or '') | |||
else | |||
desc = desc .. ' until the end of the attack' | |||
end | |||
elseif effect['type'] == 'Sleep' or effect['type'] == 'Stun' then | |||
local effName = (effect['type'] == 'Sleep' and 'sleep' or string.gsub(effect['flavour'], '^(%a)', function(c) return string.lower(c) end)) | |||
if effect['chance'] < 100 then | |||
desc = 'has a ' .. Shared.round(effect['chance'], 2, 0) .. '%' .. ' chance to apply ' .. effName | |||
else | |||
desc = 'applies ' .. effName | |||
end | |||
desc = desc .. ' for ' .. Shared.formatnum(math.floor(effect['turns'])) .. ' turn' .. (effect['turns'] > 1 and 's' or '') | |||
else | |||
desc = 'Unknown effect type: ' .. effect['type'] .. '[[Category:Pages with script errors]]' | |||
end | |||
table.insert(descPart, desc) | |||
end | |||
return Shared.joinList(descPart, '; ', ' and ') | |||
end | |||
-- Generates a textual description for a special attack | |||
-- Similar to in game function describeAttack in attack.js | |||
function p._getSpecialAttackDescription(attack, targetIsEnemy) | |||
local attackDescriptors = { | |||
["count"] = function(attack, attName, tarName) return attack['attackCount'] end, | |||
["damage"] = function(attack, attName, tarName) return p._getDamageDescription(attack['damage'], attName, tarName) end, | |||
["lifesteal"] = function(attack, attName, tarName) return Shared.round(attack['lifesteal'], 2, 0) .. '%' end, | |||
["target"] = function(attack, attName, tarName) return nouns[tarName]['plain'] end, | |||
["attacker"] = function(attack, attName, tarName) return nouns[attName]['plain'] end, | |||
["tarPos"] = function(attack, attName, tarName) return nouns[tarName]['possesive'] end, | |||
["attPos"] = function(attack, attName, tarName) return nouns[attName]['possesive'] end, | |||
["prehitEffect"] = function(attack, attName, tarName) return p._getEffectDescription(attack['prehitEffects'], attName, tarName) end, | |||
["hitEffect"] = function(attack, attName, tarName) return p._getEffectDescription(attack['onhitEffects'], attName, tarName) end, | |||
["canMiss"] = function(attack, attName, tarName) return attack['cantMiss'] and "can't miss" or "can miss" end, | |||
["avoidable"] = function(attack, attName, tarName) return attack['cantMiss'] and "unavoidable" or "avoidable" end, | |||
["duration"] = function(attack, attName, tarName) return Shared.round((attack['attackCount'] - 1) * attack['attackInterval'] / 1000, 2, 0) .. 's' end, | |||
["interval"] = function(attack, attName, tarName) return Shared.round(attack['attackInterval'] / 1000, 2, 0) .. 's' end, | |||
["tarIs"] = function(attack, attName, tarName) return nouns[tarName]['is'] end, | |||
["attIs"] = function(attack, attName, tarName) return nouns[attName]['is'] end, | |||
} | |||
-- string.gsub() searches for a pattern rather than a literal and is case sensitive, so use the below to: | |||
-- 1. Escape any characters that have special meanings within patterns | |||
-- 2. Modify the pattern to be case insensitive (by replacing each alpha char with a class containing | |||
-- both the uppercase & lowercase characters) | |||
local patternFormat = function(str, caseInsensitive) | |||
local pat = string.gsub(str, '[%(%)%.%%+%-%*%?%[%]%^%$]', function(c) return '%' .. c end) | |||
if caseInsensitive then pat = string.gsub(pat, '(%a)', function(c) return '[' .. string.upper(c) .. string.lower(c) .. ']' end) end | |||
return pat | |||
end | |||
local attackDesc = attack['description'] | |||
local attName, tarName = targetIsEnemy and 'player' or 'enemy', targetIsEnemy and 'enemy' or 'player' | |||
for desc, repFunc in pairs(attackDescriptors) do | |||
local searchPat = '<' .. patternFormat(desc, true) .. '>' | |||
if string.find(attackDesc, searchPat) ~= nil then | |||
local repText = repFunc(attack, attName, tarName) | |||
repText = patternFormat(string.upper(string.sub(repText, 1, 1)) .. string.sub(repText, 2)) | |||
attackDesc = string.gsub(attackDesc, searchPat, repText) | |||
end | |||
end | |||
return attackDesc | |||
end | |||
-- mw.log(p._getSpecialAttackDescription(p.getSpecialAttackByID(84), false)) | |||
-- mw.log(p._getSpecialAttackDescription(p.getSpecialAttackByID(31), true)) | |||
return p | return p |