Module:Monsters: Difference between revisions

Add column name for average healing
(getMonsterAttacks: Include normal attack damage when a special attack deals normal damage, even if the normal attack chance is 0%)
(Add column name for average healing)
(19 intermediate revisions by 5 users not shown)
Line 10: Line 10:


function p.getMonster(name)
function p.getMonster(name)
    return GameData.getEntityByName('monsters', name)
if name == 'Earth Golem (AoD)' then
-- Special case for ambiguous monster name
return p.getMonsterByID('melvorAoD:EarthGolem')
else
    return GameData.getEntityByName('monsters', name)
    end
end
end


function p.getMonsterByID(ID)
function p.getMonsterByID(ID)
     return GameData.getEntityByID('monsters', ID)
     return GameData.getEntityByID('monsters', ID)
end
function p.getMonsterName(monster)
if monster.id == 'melvorAoD:EarthGolem' then
-- Special case for ambiguous monster name
return 'Earth Golem (AoD)'
else
return monster.name
end
end
end


Line 39: Line 53:


function p._getMonsterStat(monster, statName)
function p._getMonsterStat(monster, statName)
if statName == 'HP' then
if statName == 'Barrier' then
return p._getMonsterBarrier(monster)
elseif statName == 'HP' then
return p._getMonsterHP(monster)
return p._getMonsterHP(monster)
elseif statName == 'maxHit' then
elseif statName == 'maxHit' then
Line 85: Line 101:
iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
elseif monster.attackType == 'random' then
elseif monster.attackType == 'random' then
iconText = Icons.Icon({monster.name, notext=notext, nolink=nolink, img='Question'})
iconText = Icons.Icon({p.getMonsterName(monster), notext=notext, nolink=nolink, img='Question'})
end
end


Line 106: Line 122:
function p._getMonsterHP(monster)
function p._getMonsterHP(monster)
return 10 * p._getMonsterLevel(monster, 'Hitpoints')
return 10 * p._getMonsterLevel(monster, 'Hitpoints')
end
function p._getMonsterBarrier(monster)
--Monster Barrier is a percentage of its max health
local barPercent = 0
if monster.barrierPercent ~= nil then
barPercent = monster.barrierPercent
end
return p._getMonsterHP(monster) * barPercent * 0.01
end
end


Line 113: Line 138:
if monster ~= nil then
if monster ~= nil then
return math.floor((p._getMonsterHP(monster)/(1 - p._getMonsterStat(monster, 'damageReduction')/100)) + 0.5)
return math.floor((p._getMonsterHP(monster)/(1 - p._getMonsterStat(monster, 'damageReduction')/100)) + 0.5)
else
return Shared.printError('No monster with that name found')
end
end
function p.getMonsterEffectiveBarrier(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster ~= nil then
return math.floor((p._getMonsterBarrier(monster)/(1 - p._getMonsterStat(monster, 'damageReduction')/100)) + 0.5)
else
return Shared.printError('No monster with that name found')
end
end
function p.getMonsterBarrier(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster ~= nil then
return p._getMonsterBarrier(monster)
else
else
return Shared.printError('No monster with that name found')
return Shared.printError('No monster with that name found')
Line 343: Line 388:
end
end


function p.getSpecAttackMaxHit(specAttack, normalMaxHit, monsterDR)
function p.getSpecAttackMaxHit(specAttack, normalMaxHit, monster)
local bestHit = 0
local bestHit = 0
for i, dmg in pairs(specAttack.damage) do
for i, dmg in pairs(specAttack.damage) do
local thisHit = 0
local thisHit = 0
Line 370: Line 416:
thisHit = normalMaxHit * 1.35
thisHit = normalMaxHit * 1.35
elseif dmg.maxRoll == "MaxHitDR" then
elseif dmg.maxRoll == "MaxHitDR" then
local monsterDR = 0
if monster ~= nil then
monsterDR = p._getMonsterStat(monster, 'damageReduction')
end
thisHit = normalMaxHit * dmg.maxPercent * 0.01 * (1 + monsterDR * 0.01)
thisHit = normalMaxHit * dmg.maxPercent * 0.01 * (1 + monsterDR * 0.01)
elseif Shared.contains({'Bleeding', 'Poisoned'}, dmg.maxRoll) then
elseif Shared.contains({'Bleeding', 'Poisoned'}, dmg.maxRoll) then
Line 384: Line 434:


function p.canSpecAttackApplyEffect(specAttack, effectType)
function p.canSpecAttackApplyEffect(specAttack, effectType)
for i, effect in pairs(specAttack.prehitEffects) do
local effectKeys = { 'prehitEffects', 'onhitEffects' }
if effect.type == effectType then
for i, effectKey in ipairs(effectKeys) do
            return true
if type(specAttack[effectKey]) == 'table' then
for j, effect in pairs(specAttack[effectKey]) do
if effect.type == effectType or p.canModifiersApplyEffect(effect.modifiers, effectType) then
return true
end
end
end
end
end
end
return false
end


for i, effect in pairs(specAttack.onhitEffects) do
function p.canModifiersApplyEffect(modifiers, effectType)
if effect.type == effectType then
-- List of modifiers which can result in the application of status effects
return true
local statusModsAll = {
["Stun"] = { 'increasedGlobalStunChance', 'increasedMeleeStunChance' },
["Sleep"] = { 'increasedGlobalSleepChance' },
["Poison"] = { 'increasedChanceToApplyPoison' },
["Slow"] = { 'increased15SlowStunChance2Turns', 'increased30Slow5TurnsChance' }
}
 
local statusMods = statusModsAll[effectType]
if statusMods ~= nil and type(modifiers) == 'table' then
for modName, modMagnitude in pairs(modifiers) do
if Shared.contains(statusMods, modName) then
return true
end
end
end
end
end
Line 405: Line 474:
elseif type(doStuns) == 'string' then
elseif type(doStuns) == 'string' then
doStuns = string.upper(doStuns) == 'TRUE'
doStuns = string.upper(doStuns) == 'TRUE'
end
-- Damage adjustments are defined as follows:
-- multiplier - Damage from modifier 'increasedDamageTaken' & additional damage while
-- stunned, asleep, or poisoned. Defined by in-game function
-- getDamageModifiers(). Applies after other percentage of flat adjustments.
-- percent - Percentage adjustments to the max hit. Applies before flat & multiplier
-- adjustments.
-- flat - Flat adjustments to the max hit. Applies after percent adjustments, and
-- after multiplier adjustments.
local dmgAdjust = { ["percent"] = 100, ["flat"] = 0, ["multiplier"] = 100 }
-- Check passives & effects that apply pre or on hit for damage modifiers
local dmgMods = {
-- List of modifiers which affect damage dealt, and whether they are percentage or flat adjustments
["increasedDamageTaken"] = { type = 'multiplier', mult = 1 },
["increasedMaxHitPercent"] = { type = 'percent', mult = 1 },
["increasedMeleeMaxHit"] = { type = 'percent', mult = 1 },
["increasedRangedMaxHit"] = { type = 'percent', mult = 1 },
["increasedMagicMaxHit"] = { type = 'percent', mult = 1 },
["increasedMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedMeleeMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedRangedMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedMagicMaxHitFlat"] = { type = 'flat', mult = 10 },
-- Rage: +2% max hit per stack, maximum of 10 stacks
["increasedRage"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 10 },
-- Dark Blade: +1% max hit per successful hit, maximum of 30 stacks
["increasedChanceDarkBlade"] = { type = 'percent', mult = 1, magnitude = 1, maxStacks = 30 },
-- Growing Madness/Moment in Time/Reign Over Time: +2% max hit per stack, maximum of 25 stacks
["growingMadnessPassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 },
["momentInTimePassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 },
["reignOverTimePassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 }
}
local effectKeys = { 'prehitEffects', 'onhitEffects' }
local dmgStatuses = {
-- List of status effects which can affect damage dealt
["Stun"] = { type = 'multiplier', magnitude = 30 },
["Sleep"] = { type = 'multiplier', magnitude = 20 }
}
local canApplyStatus = {}
-- Initialize table
for statusName, def in pairs(dmgStatuses) do
canApplyStatus[statusName] = false
end
local adjustForMod = function(mod, modMagnitude, effect)
local magnitude = mod.magnitude or modMagnitude
local maxStacks = mod.maxStacks or (effect ~= nil and effect.maxStacks) or 1
dmgAdjust[mod.type] = dmgAdjust[mod.type] + magnitude * mod.mult * maxStacks
end
local adjustForCurse = function(curseID, effect)
local curse = Magic.getSpellByID(curseID, 'curse')
if type(curse) == 'table' and type(curse.targetModifiers) == 'table' then
for modName, modMagnitude in pairs(curse.targetModifiers) do
local mod = dmgMods[modName]
if mod ~= nil then
-- The modifier is one which affects damage dealt
adjustForMod(mod, modMagnitude, effect)
end
end
end
end
-- Check monster passives for modifiers which affect damage dealt, and alo if any modifiers
-- present can apply stun or sleep
if monster ~= nil and type(monster.passives) ~= nil then
for i, passiveID in ipairs(monster.passives) do
local passive = p.getPassiveByID(passiveID)
if passive ~= nil and type(passive.modifiers) == 'table' then
for modName, modMagnitude in pairs(passive.modifiers) do
local mod = dmgMods[modName]
if modName == 'applyRandomCurseOnSpawn' then
-- Special case in which the enemy can apply a random curse. Currently
-- Anguish III is the curse with the highest +% damage taken, so use this.
adjustForCurse('melvorF:AnguishIII')
elseif mod ~= nil then
-- The modifier is one which affects damage dealt
adjustForMod(mod, modMagnitude)
end
end
-- Check for application of relevant status effects
if doStuns then
for statusName, statusDef in pairs(dmgStatuses) do
if not canApplyStatus[statusName] and p.canModifiersApplyEffect(passive.modifiers, statusName) then
canApplyStatus[statusName] = true
end
end
end
end
end
end
end


Line 411: Line 570:
local normalMaxHit = p._getMonsterBaseMaxHit(monster)
local normalMaxHit = p._getMonsterBaseMaxHit(monster)
local hasActiveBuffSpec = false
local hasActiveBuffSpec = false
local damageMultiplier = 1
if monster.specialAttacks ~= nil then
if monster.specialAttacks ~= nil then
local canStun, canSleep = false, false
for i, specAttackID in pairs(monster.specialAttacks) do
for i, specAttackID in pairs(monster.specialAttacks) do
             local specAttack = GameData.getEntityByID('attacks', specAttackID)
             local specAttack = GameData.getEntityByID('attacks', specAttackID)
if monster.overrideSpecialChances ~= nil then
for i, effectKey in ipairs(effectKeys) do
if type(specAttack[effectKey]) == 'table' then
for j, effect in ipairs(specAttack[effectKey]) do
local countsOnPlayer = (effect.countsOn == nil or effect.countsOn == 'Attacker')
if countsOnPlayer then
-- Check for pre or on hit effects for modifiers which affect damage dealt
if type(effect.modifiers) == 'table' then
for modName, modMagnitude in pairs(effect.modifiers) do
local mod = dmgMods[modName]
if mod ~= nil then
-- The modifier is one which affects damage dealt
adjustForMod(mod, modMagnitude, effect)
end
end
end
-- Check for curses which may cause the player to incur additional damage
if effect.effectType == 'Curse' then
-- If isRandom is true then a random curse is selected. Currently
-- Anguish III is the curse with the highest +% damage taken, so
-- use this.
local curseID = (effect.isRandom and 'melvorF:AnguishIII') or effect.curse
if curseID ~= nil then
adjustForCurse(curseID, effect)
end
end
end
end
end
end
 
if monster.overrideSpecialChances ~= nil then
normalChance = normalChance - monster.overrideSpecialChances[i]
normalChance = normalChance - monster.overrideSpecialChances[i]
else
else
normalChance = normalChance - specAttack.defaultChance
normalChance = normalChance - specAttack.defaultChance
end
end
local thisMax = p.getSpecAttackMaxHit(specAttack, normalMaxHit, p._getMonsterStat(monster, 'damageReduction'))
if not canStun and p.canSpecAttackApplyEffect(specAttack, 'Stun') then canStun = true end
-- Check for application of relevant status effects
if not canSleep and p.canSpecAttackApplyEffect(specAttack, 'Sleep') then canSleep = true end
if doStuns then
for statusName, statusDef in pairs(dmgStatuses) do
if not canApplyStatus[statusName] and p.canSpecAttackApplyEffect(specAttack, statusName) then
canApplyStatus[statusName] = true
end
end
end


local thisMax = p.getSpecAttackMaxHit(specAttack, normalMaxHit, monster)
if thisMax > specialMaxHit then specialMaxHit = thisMax end
if thisMax > specialMaxHit then specialMaxHit = thisMax end
if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then  
if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then  
Line 431: Line 625:
end
end


if canSleep and doStuns then damageMultiplier = damageMultiplier * 1.2 end
if doStuns then
if canStun and doStuns then damageMultiplier = damageMultiplier * 1.3 end
for statusName, statusDef in pairs(dmgStatuses) do
if canApplyStatus[statusName] then
local adjType = statusDef.type
dmgAdjust[adjType] = dmgAdjust[adjType] + statusDef.magnitude
end
end
end
end
end
--Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
--Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
if normalChance == 0 and not hasActiveBuffSpec then normalMaxHit = 0 end
if normalChance == 0 and not hasActiveBuffSpec then normalMaxHit = 0 end
return math.floor(math.max(specialMaxHit, normalMaxHit) * damageMultiplier)
local maxHit = math.floor(math.max(specialMaxHit, normalMaxHit) * dmgAdjust.percent / 100) + dmgAdjust.flat
return math.floor(maxHit * dmgAdjust.multiplier / 100)
end
end


Line 591: Line 792:
if isNormalAttackRelevant or normalAttackChance > 0 then
if isNormalAttackRelevant or normalAttackChance > 0 then
local normalDmgText = iconText..' 1 - '..Shared.formatnum(p._getMonsterBaseMaxHit(monster))..' '..typeText..' Damage'
--Reformatting slightly - If there are any special attacks, specifically label the Normal Attack
local normalDmgText = ' 1 - '..Shared.formatnum(p._getMonsterBaseMaxHit(monster))..' '..typeText..' Damage'
if normalAttackChance > 0 and normalAttackChance < 100 then
if normalAttackChance > 0 and normalAttackChance < 100 then
normalDmgText = normalAttackChance .. '% ' .. normalDmgText
normalDmgText = normalAttackChance .. '% ' ..iconText..' Normal Attack\r\n** '..normalDmgText
elseif hasActiveBuffSpec and normalAttackChance == 0 then
elseif hasActiveBuffSpec and normalAttackChance == 0 then
--If the monster normally has a 0% chance of doing a normal attack but some special attacks can't be repeated, include it
--If the monster normally has a 0% chance of doing a normal attack but some special attacks can't be repeated, include it
--(With a note about when it does it)
--(With a note about when it does it)
normalDmgText = normalDmgText .. ' (Instead of repeating '..table.concat(buffAttacks, ' or ')..' while the effect is already active)'
normalDmgText = iconText..' Normal Attack\r\n** '..normalDmgText .. ' (Instead of repeating '..table.concat(buffAttacks, ' or ')..' while the effect is already active)'
end
end
result = '* ' .. normalDmgText .. result
result = '* ' .. normalDmgText .. result
Line 681: Line 884:


return result
return result
end
function p.getMonsterBoxBarrierText(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster == nil then
return Shared.printError('No monster with that name found')
end
local barrier = p._getMonsterBarrier(monster)
if barrier == 0 then
return ''
end
local result = {}
table.insert(result, '|-\r\n| style="font-weight: bold;" | [[Barrier]]:')
table.insert(result, '\r\n| colspan=15 style="text-align: right" |')
table.insert(result, Icons.Icon({"Barrier", notext="true"}))
table.insert(result, ' '..barrier)
return table.concat(result, '')
end
end


Line 736: Line 960:
local bones = p._getMonsterBones(monster)
local bones = p._getMonsterBones(monster)
local boneVal = 0
local boneVal = 0
local barrierDust = Items.getItemByID("melvorAoD:Barrier_Dust")
local dustVal = 0
--Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
--Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
if bones ~= nil then
if bones ~= nil then
local boneQty = (bones.quantity ~= nil and bones.quantity or 1)
local boneQty = (bones.quantity ~= nil and bones.quantity or 1)
local barrier = p._getMonsterBarrier(monster)
result = result.."'''Always Drops:'''"
result = result.."'''Always Drops:'''"
result = result..'\r\n{|class="wikitable" id="bonedrops"'
result = result..'\r\n{|class="wikitable" id="bonedrops"'
result = result..'\r\n!Item !! Qty'
result = result..'\r\n!Item !! Qty'
result = result..'\r\n|-\r\n|'..Icons.Icon({bones.item.name, type='item'})
result = result..'\r\n|-\r\n|'..Icons.Icon({bones.item.name, type='item'})
result = result..'||'..boneQty..'\r\n'..'|}'
result = result..'||'..boneQty
if barrier > 0 then
local dustQty = math.max(math.floor(barrier / 10 / 20), 1)
result = result..'\r\n|-\r\n|'..Icons.Icon({barrierDust.name, type='item'})
result = result..'||'..dustQty
dustVal = dustQty * barrierDust.sellsFor
end
result = result..'\r\n'..'|}'
boneVal = boneQty * bones.item.sellsFor
boneVal = boneQty * bones.item.sellsFor
end
end
Line 845: Line 1,079:
result = result..' and bones'
result = result..' and bones'
end
end
result = result..', the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue + boneVal, 2, 0))..'.'
if dustVal > 0 then
result = result..' and barrier dust'
end
result = result..', the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue + boneVal + dustVal, 2, 0))..'.'
end
end
end
end
Line 958: Line 1,195:
return Shared.printError(chestName .. ' does not have a drop table')
return Shared.printError(chestName .. ' does not have a drop table')
else
else
local lootValue = 0
 
local function formatNumRange(minValue, maxValue)
if maxValue ~= nil and maxValue > minValue then
return Shared.formatnum(minValue) .. ' - ' .. Shared.formatnum(maxValue)
else
return Shared.formatnum(minValue)
end
end
 
local lootValue, foodValue = 0, 0
local totalWt = 0
local totalWt = 0
for i, row in pairs(chest.dropTable) do
local isAllFood = true
for i, row in ipairs(chest.dropTable) do
totalWt = totalWt + row.weight
totalWt = totalWt + row.weight
if isAllFood then
-- If the container's contents are entirely food then we add additional
-- information to the output, so we determine this here
local item = Items.getItemByID(row.itemID)
if item ~= nil and item.healsFor == nil then
isAllFood = false
end
end
end
end
result = result..'\r\n{|class="wikitable sortable"'
result = result..'\r\n{|class="wikitable sortable"'
result = result..'\r\n!Item!!Qty'
result = result..'\r\n!Item!!Qty'
result = result..'!!colspan="2"|Chance!!Price'
result = result..'!!colspan="2"|Chance!!Price' .. (isAllFood and '!!Healing!!Avg. Healing' or '')


--Sort the loot table by weight in descending order
--Sort the loot table by weight in descending order
local chestDrops = {}
local chestDrops = Shared.shallowClone(chest.dropTable)
for i, row in ipairs(chest.dropTable) do
table.insert(chestDrops, row)
end
table.sort(chestDrops, function(a, b) return a.weight > b.weight end)
table.sort(chestDrops, function(a, b) return a.weight > b.weight end)
for i, row in ipairs(chestDrops) do
for i, row in ipairs(chestDrops) do
local thisItem = Items.getItemByID(row.itemID)
local thisItem = Items.getItemByID(row.itemID)
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
result = result..'||style="text-align:right" data-sort-value="'..(row.minQuantity + row.maxQuantity)..'"|'
result = result..'||style="text-align:right" data-sort-value="'..(row.minQuantity + row.maxQuantity)..'"| ' .. formatNumRange(row.minQuantity, row.maxQuantity)
 
if row.minQuantity < row.maxQuantity then
result = result .. Shared.formatnum(row.minQuantity) .. ' - ' .. Shared.formatnum(row.maxQuantity)
else
result = result .. Shared.formatnum(row.minQuantity)
end


local dropChance = (row.weight / totalWt) * 100
local dropChance = (row.weight / totalWt) * 100
Line 997: Line 1,243:
end
end
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor * ((row.minQuantity + row.maxQuantity)/ 2))
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor * ((row.minQuantity + row.maxQuantity)/ 2))
if isAllFood then
local hp = thisItem.healsFor * 10
local minHeal, maxHeal = hp * row.minQuantity, hp * row.maxQuantity
local avgHpPerLoot = (dropChance * 0.01 * (minHeal + maxHeal) / 2)
foodValue = foodValue + avgHpPerLoot
result = result .. '||data-sort-value="' .. thisItem.healsFor .. '"'
result = result .. '|' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. formatNumRange(minHeal, maxHeal)
result = result .. '||data-sort-value="' .. avgHpPerLoot .. '"'
result = result .. '|' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. Shared.round(avgHpPerLoot, 2, 0)
end
end
end
result = result..'\r\n|}'
result = result..'\r\n|}'
result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))..'.'
result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))..'.'
if isAllFood then
result = result..'\r\n\r\nThe average healing of the contents of one chest is ' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. Shared.round(foodValue, 2, 0) .. '.'
end
end
end


Line 1,015: Line 1,275:
return p.getDungeonMonsterTable(frame)
return p.getDungeonMonsterTable(frame)
end
end
 
local tableTxt = '{| class="wikitable sortable"'
tableTxt = tableTxt..'\r\n! Name !! Combat Level !! Hitpoints !! colspan=2| Max Hit !! [[Combat Triangle|Combat Style]]'
local monsters = {}
local hasBarrier = false
for i, monsterID in ipairs(area.monsterIDs) do
for i, monsterID in ipairs(area.monsterIDs) do
local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({monster.name, type='monster'})
if not hasBarrier and p._getMonsterBarrier(monster) > 0 then
tableTxt = tableTxt..'||'..p._getMonsterCombatLevel(monster)
hasBarrier = true
tableTxt = tableTxt..'||'..Shared.formatnum(p._getMonsterHP(monster))
end
table.insert(monsters, monster)
end
 
local tableBits = {}
table.insert(tableBits, '{| class="wikitable sortable"')
table.insert(tableBits, '\r\n! Name !! Combat Level ')
if hasBarrier then
table.insert(tableBits, '!! [[Barrier]] ')
end
table.insert(tableBits, '!! Hitpoints !! colspan=2| Max Hit !! [[Combat Triangle|Combat Style]]')
for i, monster in ipairs(monsters) do
local rowBits = {}
table.insert(tableBits, '\r\n|-\r\n|'..Icons.Icon({p.getMonsterName(monster), type='monster'}))
table.insert(tableBits, '||'..p._getMonsterCombatLevel(monster))
if hasBarrier then
table.insert(tableBits, '||'..Shared.formatnum(p._getMonsterBarrier(monster)))
end
table.insert(tableBits, '||'..Shared.formatnum(p._getMonsterHP(monster)))
local drReduction = p._getMonsterDrReduction(monster)
local drReduction = p._getMonsterDrReduction(monster)
local maxHit = p._getMonsterMaxHit(monster)
local maxHit = p._getMonsterMaxHit(monster)
if drReduction > 0 then
if drReduction > 0 then
tableTxt = tableTxt..'||style="text-align:right" data-sort-value="'..maxHit..'"| -'..drReduction..'% DR'
table.insert(tableBits, '||style="text-align:right" data-sort-value="'..maxHit..'"| -'..drReduction..'% DR')
tableTxt = tableTxt..'||style="text-align:right"|'..Shared.formatnum(maxHit)
table.insert(tableBits, '||style="text-align:right"|'..Shared.formatnum(maxHit))
else
else
tableTxt = tableTxt..'||style="text-align:right" colspan="2" data-sort-value="'..maxHit..'"|'..Shared.formatnum(maxHit)
table.insert(tableBits, '||style="text-align:right" colspan="2" data-sort-value="'..maxHit..'"|'..Shared.formatnum(maxHit))
end
end
tableTxt = tableTxt..'||'..p._getMonsterStyleIcon({monster, nolink=true})
table.insert(tableBits, '||'..p._getMonsterStyleIcon({monster, nolink=true}))
end
end
tableTxt = tableTxt..'\r\n|}'
table.insert(tableBits, '\r\n|}')
return tableTxt
return table.concat(tableBits, '')
end
end


Line 1,046: Line 1,327:
--For Dungeons, go through and count how many of each monster are in the dungeon first
--For Dungeons, go through and count how many of each monster are in the dungeon first
local monsterCounts = {}
local monsterCounts = {}
local monsters = {}
local hasBarrier = false
for i, monsterID in ipairs(area.monsterIDs) do
for i, monsterID in ipairs(area.monsterIDs) do
if monsterCounts[monsterID] == nil then
if monsterCounts[monsterID] == nil then
Line 1,051: Line 1,334:
else
else
monsterCounts[monsterID] = monsterCounts[monsterID] + 1
monsterCounts[monsterID] = monsterCounts[monsterID] + 1
if monsterID ~= 'melvorF:RandomITM' and monsterID ~= 'melvorTotH:RandomSpiderLair' then
monsters[monsterID] = p.getMonsterByID(monsterID)
if not hasBarrier and p._getMonsterBarrier(monsters[monsterID]) > 0 then
hasBarrier = true
end
end
end
end
end
end
Line 1,058: Line 1,347:
-- Declare function for building table rows to avoid repeating code
-- Declare function for building table rows to avoid repeating code
local buildRow = function(entityID, monsterCount, specialType)
local buildRow = function(entityID, monsterCount, specialType)
local monIcon, monLevel, monHP, monMaxHit, monStyle, monCount, monDrReduce
local monIcon, monLevel, monHP, monMaxHit, monStyle, monCount, monDrReduce, monBarrier
local monData = {}
local monData = {}
if specialType ~= nil and Shared.contains({'Afflicted', 'Spider', 'SlayerArea'}, specialType) then
if specialType ~= nil and Shared.contains({'Afflicted', 'Spider', 'SlayerArea'}, specialType) then
Line 1,065: Line 1,354:
local iconQ = Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
local iconQ = Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
monIcon = Icons.Icon({'Into the Mist', 'Afflicted Monster', nolink=true, img='Question'})
monIcon = Icons.Icon({'Into the Mist', 'Afflicted Monster', nolink=true, img='Question'})
monLevel, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
monLevel, monBarrier, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
elseif specialType == 'Spider' then
elseif specialType == 'Spider' then
local iconQ = Icons.Icon({'', notext=true, nolink=true, img='Question'})
local iconQ = Icons.Icon({'', notext=true, nolink=true, img='Question'})
Line 1,072: Line 1,361:
local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
if monster ~= nil then
if monster ~= nil then
table.insert(monIconPart, Icons.Icon({monster.name, type='monster'}))
table.insert(monIconPart, Icons.Icon({p.getMonsterName(monster), type='monster'}))
end
end
end
end
monIcon = table.concat(monIconPart, '<br/>')
monIcon = table.concat(monIconPart, '<br/>')
monLevel, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
monLevel, monBarrier, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
elseif specialType == 'SlayerArea' then
elseif specialType == 'SlayerArea' then
-- entityID corresponds to a slayer area
-- entityID corresponds to a slayer area
Line 1,082: Line 1,371:
monIcon = Icons.Icon({area.name, type='combatArea'}) .. ' Monsters'
monIcon = Icons.Icon({area.name, type='combatArea'}) .. ' Monsters'
monLevel = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterCombatLevel(monster) end)}
monLevel = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterCombatLevel(monster) end)}
if hasBarrier then
monBarrier = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterBarrier(monster) end)}
end
monHP = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterHP(monster) end)}
monHP = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterHP(monster) end)}
local lowMaxHit, highMaxHit = p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterMaxHit(monster) end)
local lowMaxHit, highMaxHit = p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterMaxHit(monster) end)
Line 1,093: Line 1,385:
-- entityID corresponds to a monster
-- entityID corresponds to a monster
local monster = p.getMonsterByID(entityID)
local monster = p.getMonsterByID(entityID)
monIcon = Icons.Icon({monster.name, type='monster'})
monIcon = Icons.Icon({p.getMonsterName(monster), type='monster'})
monLevel = p._getMonsterCombatLevel(monster)
monLevel = p._getMonsterCombatLevel(monster)
if hasBarrier then
monBarrier = p._getMonsterBarrier(monster)
end
monHP = p._getMonsterHP(monster)
monHP = p._getMonsterHP(monster)
monDrReduce = p._getMonsterDrReduction(monster)
monDrReduce = p._getMonsterDrReduction(monster)
Line 1,117: Line 1,412:
return Shared.formatnum(val[1]) .. ' - ' .. Shared.formatnum(val[2])
return Shared.formatnum(val[1]) .. ' - ' .. Shared.formatnum(val[2])
else
else
return val[1] .. ' - ' .. val[2]
return val[1] .. ' - ' .. val[2]
end
end
elseif type(val) == 'number' then
elseif type(val) == 'number' then
return Shared.formatnum(val)
return Shared.formatnum(val)
else
else
return val
return val
end
end
end
end
local resultPart = {}
local resultPart = {}
table.insert(resultPart, '\r\n|-\r\n| ' .. monIcon)
table.insert(resultPart, '\r\n|-\r\n| ' .. monIcon)
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monLevel) .. '"| ' .. getValText(monLevel))
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monLevel) .. '"| ' .. getValText(monLevel))
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monHP) .. '"| ' .. getValText(monHP))
if hasBarrier then
if type(monDrReduce) == 'number' and monDrReduce > 0 then
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monBarrier) .. '"| ' .. getValText(monBarrier))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="'..getValSort(monMaxHit)..'"| -'..monDrReduce..'% DR')
end
table.insert(resultPart, '\r\n|style="text-align:right"|'..getValText(monMaxHit))
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monHP) .. '"| ' .. getValText(monHP))
else
if type(monDrReduce) == 'number' and monDrReduce > 0 then
table.insert(resultPart, '\r\n|style="text-align:right" colspan="2" data-sort-value="'..getValSort(monMaxHit)..'"|'..getValText(monMaxHit))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="'..getValSort(monMaxHit)..'"| -'..monDrReduce..'% DR')
end
table.insert(resultPart, '\r\n|style="text-align:right"|'..getValText(monMaxHit))
table.insert(resultPart, '\r\n| ' .. monStyle)
else
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monCount) .. '"| ' .. getValText(monCount))
table.insert(resultPart, '\r\n|style="text-align:right" colspan="2" data-sort-value="'..getValSort(monMaxHit)..'"|'..getValText(monMaxHit))
return table.concat(resultPart)
end
table.insert(resultPart, '\r\n| ' .. monStyle)
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monCount) .. '"| ' .. getValText(monCount))
return table.concat(resultPart)
end
 
local returnPart = {}
table.insert(returnPart, '{| class="wikitable sortable"')
table.insert(returnPart, '\r\n! Name !! Combat Level ')
if hasBarrier then
table.insert(returnPart, '!! [[Barrier]] ')
end
end
 
table.insert(returnPart, '!! Hitpoints !! colspan="2" | Max Hit !! [[Combat Triangle|Combat Style]] !! Count')
local returnPart = {}
table.insert(returnPart, '{| class="wikitable sortable"')
table.insert(returnPart, '\r\n! Name !! Combat Level !! Hitpoints !! colspan="2" | Max Hit !! [[Combat Triangle|Combat Style]] !! Count')
-- Special handing for Impending Darkness event
-- Special handing for Impending Darkness event
-- TODO needs to be revised once there is a better understanding of how the event works
-- TODO needs to be revised once there is a better understanding of how the event works
--if area.isEvent ~= nil and area.isEvent then
-- for i, eventAreaID in ipairs(Areas.eventData.slayerAreas) do
-- table.insert(returnPart, buildRow(eventAreaID, {5, 8}, 'SlayerArea'))
-- end
--  -- Add Bane * 4
--  table.insert(returnPart, buildRow(152, 4))
--end
for i, monsterID in ipairs(area.monsterIDs) do
for i, monsterID in ipairs(area.monsterIDs) do
if not Shared.contains(usedMonsters, monsterID) then
if not Shared.contains(usedMonsters, monsterID) then
Line 1,161: Line 1,456:
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], 'Spider'))
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], 'Spider'))
else
else
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID]))
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], hasBarrier))
end
end
table.insert(usedMonsters, monsterID)
table.insert(usedMonsters, monsterID)
Line 1,189: Line 1,484:
for i, monsterID in ipairs(area.monsterIDs) do
for i, monsterID in ipairs(area.monsterIDs) do
local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
table.insert(monsterList, Icons.Icon({monster.name, type='monster'}))
table.insert(monsterList, Icons.Icon({p.getMonsterName(monster), type='monster'}))
end
end
return table.concat(monsterList, '<br/>')
return table.concat(monsterList, '<br/>')
Line 1,198: Line 1,493:
local lastID = ''
local lastID = ''
local count = 0
local count = 0
-- Special handing for Impending Darkness event
-- TODO needs to be revised once there is a better understanding of how the event works
--if area.isEvent ~= nil and area.isEvent then
-- for i, eventAreaID in ipairs(Areas.eventData.slayerAreas) do
-- local eventArea = Areas.getAreaByID('slayer', eventAreaID)
-- table.insert(monsterList, '5-8 ' .. Icons.Icon({eventArea.name, type='combatArea'}) .. ' Monsters')
-- end
-- table.insert(monsterList, '4 ' .. Icons.Icon({'Bane', type='monster'}))
--end
local monsterCounts = {}
local monsterCounts = {}
Line 1,232: Line 1,518:
local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
if monster ~= nil then
if monster ~= nil then
table.insert(monIconPart, '&nbsp;&nbsp;&nbsp;' .. Icons.Icon({monster.name, type='monster'}))
table.insert(monIconPart, '&nbsp;&nbsp;&nbsp;' .. Icons.Icon({p.getMonsterName(monster), type='monster'}))
end
end
end
end
Line 1,238: Line 1,524:
else
else
local monsterObj = p.getMonsterByID(monster.id)
local monsterObj = p.getMonsterByID(monster.id)
table.insert(monsterList, Icons.Icon({monsterObj.name, type='monster', qty=monster.count}))
table.insert(monsterList, Icons.Icon({p.getMonsterName(monsterObj), type='monster', qty=monster.count}))
end
end
end
end
Line 1,265: Line 1,551:
if monster.gpDrops ~= nil and monster.gpDrops.max > 0 then
if monster.gpDrops ~= nil and monster.gpDrops.max > 0 then
local avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
local avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
result = result .. '<br/>' .. monster.name .. ',' .. monster.gpDrops.min .. ',' .. monster.gpDrops.max .. ',' .. avgGp
result = result .. '<br/>' .. p.getMonsterName(monster) .. ',' .. monster.gpDrops.min .. ',' .. monster.gpDrops.max .. ',' .. avgGp
end
end
end
end
Line 1,335: Line 1,621:
local monsterGP = p._getMonsterAverageGP(monster)
local monsterGP = p._getMonsterAverageGP(monster)
local combatLevel = p._getMonsterCombatLevel(monster)
local combatLevel = p._getMonsterCombatLevel(monster)
result = result..'\r\n|-\r\n|'..Icons.Icon({monster.name, type='monster', noicon=true})..'||'..combatLevel..'||'..monsterGP
result = result..'\r\n|-\r\n|'..Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true})..'||'..combatLevel..'||'..monsterGP
end
end
end
end
Line 1,428: Line 1,714:
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({monster.name, type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. p._getMonsterHP(monster) .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. p._getMonsterHP(monster) .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
Line 1,467: Line 1,753:
-- Generate row per monster
-- Generate row per monster
for i, monster in ipairs(GameData.rawData.monsters) do
for i, monster in ipairs(GameData.rawData.monsters) do
if monster.name ~= nil then
if p.getMonsterName(monster) ~= nil then
local cmbLevel = p._getMonsterCombatLevel(monster)
local cmbLevel = p._getMonsterCombatLevel(monster)
Line 1,484: Line 1,770:
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({monster.name, type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
Line 1,515: Line 1,801:
-- Generate row per monster
-- Generate row per monster
for i, monster in ipairs(GameData.rawData.monsters) do
for i, monster in ipairs(GameData.rawData.monsters) do
if monster.name ~= nil then
if p.getMonsterName(monster) ~= nil then
local cmbLevel = p._getMonsterCombatLevel(monster)
local cmbLevel = p._getMonsterCombatLevel(monster)
Line 1,539: Line 1,825:
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({monster.name, type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
-- table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
-- table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
Line 1,578: Line 1,864:
spAttTable[spAtt.id]['icons'][attChance] = {}
spAttTable[spAtt.id]['icons'][attChance] = {}
end
end
table.insert(spAttTable[spAtt.id]['icons'][attChance], Icons.Icon({ monster.name, type = 'monster' }))
table.insert(spAttTable[spAtt.id]['icons'][attChance], Icons.Icon({ p.getMonsterName(monster), type = 'monster' }))
end
end
end
end
Line 1,623: Line 1,909:
for i, monsterID in ipairs(area.monsterIDs) do
for i, monsterID in ipairs(area.monsterIDs) do
local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
table.insert(outArray, "===={{MonsterIcon|"..monster.name.."|size=40}}====")
table.insert(outArray, "===={{MonsterIcon|"..p.getMonsterName(monster).."|size=40}}====")
table.insert(outArray, "{{MonsterDrops|"..monster.name.."|size=40}}")
table.insert(outArray, "{{MonsterDrops|"..p.getMonsterName(monster).."|size=40}}")
end
end
return table.concat(outArray, "\r\n")
return table.concat(outArray, "\r\n")
3

edits