Module:Monsters: Difference between revisions

time to see what breaks
(Fixed bug with slayer table for Master tier)
(time to see what breaks)
Line 33: Line 33:
   result.id = ID
   result.id = ID
   return result
   return result
end
function p.getSpecialAttack(name)
  local result = nil
  for i, attack in pairs(MonsterData.SpecialAttacks) do
    if(attack.name == name) then
      result = Shared.clone(attack)
      --Make sure every attack has an ID, and account for the 1-based indexing of Lua
      result.id = i - 1
      break
    end
  end
  return result
end
function p.getSpecialAttackByID(ID)
  return MonsterData.SpecialAttacks[ID + 1]
end
end


Line 69: Line 51:
function p.getPassiveByID(ID)
function p.getPassiveByID(ID)
   return MonsterData.Passives[ID + 1]
   return MonsterData.Passives[ID + 1]
end
function p._getMonsterStat(monster, statName)
  if statName == 'HP' then
    return p._getMonsterHP(monster)
  elseif statName == 'maxHit' then
    return p._getMonsterMaxHit(monster)
  elseif statName == 'accuracyRating' then
    return p._getMonsterAR(monster)
  elseif statName == 'meleeEvasionRating' then
    return p._getMonsterER(monster, 'Melee')
  elseif statName == 'rangedEvasionRating' then
    return p._getMonsterER(monster, 'Ranged')
  elseif statName == 'magicEvasionRating' then
    return p._getMonsterER(monster, 'Magic')
  end
  return monster[StatName]
end
end


Line 79: Line 79:
   end
   end


   if StatName == 'HP' then
   return p._getMonsterStat(monster, StatName)
    return p._getMonsterHP(monster)
  elseif StatName == 'maxHit' then
    return p._getMonsterMaxHit(monster)
  elseif StatName == 'accuracyRating' then
    return p._getMonsterAR(monster)
  elseif StatName == 'meleeEvasionRating' then
    return p._getMonsterER({monster, 'Melee'})
  elseif StatName == 'rangedEvasionRating' then
    return p._getMonsterER({monster, 'Ranged'})
  elseif StatName == 'magicEvasionRating' then
    return p._getMonsterER({monster, 'Magic'})
  end
 
  return monster[StatName]
end
end


Line 103: Line 89:


   local iconText = ''
   local iconText = ''
   if  Constants.getCombatStyleName(monster.attackType) == 'Melee' then
   if  monster.attackType == 'melee' then
     iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
   elseif monster.attackType == 'ranged' then
     iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
   elseif monster.attackType == 'magic' then
     iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
   end
   end
Line 128: Line 114:


function p._getMonsterHP(monster)
function p._getMonsterHP(monster)
   return monster.hitpoints * 10
   return 10 * p.getMonsterLevel(monster, 'Hitpoints')
end
end


Line 139: Line 125:
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end
end
function p.getMonsterLevel(monster, skillName)
  local result = 0
  if monster.levels[skillName] ~= nil then
    result = monster.levels[skillName]
  end
  return result
end
function p.getEquipmentStat(monster, statName)
  local result = 0
  for i, stat in Shared.skpairs(monster.equipmentStats) do
    if stat.key == statName then
      result = stat.value
      break
    end
  end
  return result
end
function p.calculateStandardStat(effectiveLevel, bonus)
  --Based on calculateStandardStat in Characters.js
  return (effectiveLevel + 9) * (bonus + 64)
end
function p.calculateStandardMaxHit(baseLevel, strengthBonus)
  --Based on calculateStandardMaxHit in Characters.js
  local effectiveLevel = baseLevel + 9
  return math.floor(10 * (1.3 + effectiveLevel / 10 + strengthBonus / 80 + effectiveLevel * strengthBonus / 640))
end
end


function p._getMonsterAttackSpeed(monster)
function p._getMonsterAttackSpeed(monster)
   return monster.attackSpeed / 1000
   return p._getEquipmentStat(monster, 'attackSpeed') / 1000
end
end


Line 156: Line 172:


function p._getMonsterCombatLevel(monster)
function p._getMonsterCombatLevel(monster)
   local base = 0.25 * (monster.defenceLevel + monster.hitpoints)
   local base = 0.25 * (p.getMonsterLevel(monster, 'Defence') + p.getMonsterLevel(monster, 'Hitpoints'))
   local melee = 0.325 * (monster.attackLevel + monster.strengthLevel)
   local melee = 0.325 * (p.getMonsterLevel(monster, 'Attack') + p.getMonsterLevel(monster, 'Strength'))
   local range = 0.325 * (1.5 * monster.rangedLevel)
   local range = 0.325 * (1.5 * p.getMonsterLevel(monster, 'Ranged'))
   local magic = 0.325 * (1.5 * monster.magicLevel)
   local magic = 0.325 * (1.5 * p.getMonsterLevel(monster, 'Magic'))
   if melee > range and melee > magic then
   if melee > range and melee > magic then
     return math.floor(base + melee)
     return math.floor(base + melee)
Line 181: Line 197:


function p._getMonsterAR(monster)
function p._getMonsterAR(monster)
   local effAttLvl = 0
   local baseLevel = 0
   local attBonus = 0
   local bonus = 0
   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
   if monster.attackType == 'melee' then
     effAttLvl = monster.attackLevel + 9
     baseLevel = p.getMonsterLevel(monster, 'Attack')
     attBonus = monster.attackBonus + 64
     bonus = p.getEquipmentStat(monster, 'stabAttackBonus')
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
   elseif monster.attackType == 'ranged' then
     effAttLvl = monster.rangedLevel + 9
     baseLevel = p.getMonsterLevel(monster, 'Ranged')
     attBonus = monster.attackBonusRanged + 64
     bonus = p.getEquipmentStat(monster, 'rangedAttackBonus')
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
   elseif monster.attackType == 'magic' then
     effAttLvl = monster.magicLevel + 9
     baseLevel = p.getMonsterLevel(monster, 'Magic')
     attBonus = monster.attackBonusMagic + 64
     bonus = p.getEquipmentStat(monster, 'magicAttackBonus')
   else
   else
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
   end
   end


   return effAttLvl * attBonus
   return p.calculateStandardStat(baseLevel, bonus)
end
end


Line 210: Line 226:
end
end


function p._getMonsterER(frame)
function p._getMonsterER(monster, style)
   local args = frame.args ~= nil and frame.args or frame
   local baseLevel= 0
   local monster = args[1]
   local bonus = 0
  local style = args[2]


  local effDefLvl = 0
  local defBonus = 0
   if style == "Melee" then
   if style == "Melee" then
     effDefLvl = monster.defenceLevel + 9
     baseLevel = p.getMonsterLevel(monster, 'Defence')
     defBonus = monster.defenceBonus + 64
     bonus = p.getEquipmentStat(monster, 'meleeDefenceBonus')
   elseif style == "Ranged" then
   elseif style == "Ranged" then
     effDefLvl = monster.defenceLevel + 9
     baseLevel = p.getMonsterLevel(monster, 'Defence')
     defBonus = monster.defenceBonusRanged + 64
     bonus = p.getEquipmentStat(monster, 'rangedDefenceBonus')
   elseif style == "Magic" then
   elseif style == "Magic" then
     effDefLvl = math.floor(monster.magicLevel * 0.7) + math.floor(monster.defenceLevel * 0.3) + 9
     baseLevel = math.floor(p.getMonsterLevel(monster, 'Magic') * 0.7 + p.getMonsterLevel(monster, 'Defence') * 0.3)
     defBonus = monster.defenceBonusMagic + 64
     bonus = p.getEquipmentStat(monster, 'magicDefenceBonus')
   else
   else
     return "ERROR: Must choose Melee, Ranged, or Magic[[Category:Pages with script errors]]"
     return "ERROR: Must choose Melee, Ranged, or Magic[[Category:Pages with script errors]]"
   end
   end
   return effDefLvl * defBonus
 
   return p.calculateStandardStat(baseLevel, bonus)
end
end


Line 242: Line 256:
   end
   end


   return p._getMonsterER({monster, style})
   return p._getMonsterER(monster, style)
end
end


Line 294: Line 308:


   return p._getMonsterAreas(monster, hideDungeons)
   return p._getMonsterAreas(monster, hideDungeons)
end
function p.getSpecAttackMaxHit(specAttack, normalMaxHit)
  local result = 0
  for i, dmg in pairs(specAttack.damage) do
    if dmg.maxRoll == 'Fixed' then
      result = dmg.maxPercent * 10
    elseif dmgRoll == "MaxHit" then
      if dmg.character == 'Target' then
        --Confusion applied damage based on the player's max hit. Gonna just ignore that one
        result = 0
      else
        result = dmg.maxPercent * normalMaxHit
      end
    end
  end
  return result
end
function p.canSpecAttackApplyEffect(specAttack, effectType)
  local result = false
  for i, effect in pairs(specAttack.prehitEffects) do
    if effect.type == effectType then
      result = true
      break
    end
  end
  for i, effect in pairs(specAttack.onhitEffects) do
    if effect.type == effectType then
      result = true
      break
    end
  end
  return result
end
end


Line 310: Line 359:
   local hasActiveBuffSpec = false
   local hasActiveBuffSpec = false
   local damageMultiplier = 1
   local damageMultiplier = 1
   if monster.hasSpecialAttack then
   if monster.specialAttacks[1] ~= nil then
     local canStun = false
     local canStun = false
     local canSleep = false
     local canSleep = false
     for i, specID in pairs(monster.specialAttackID) do
     for i, specAttack in pairs(monster.specialAttacks) do
      local specAttack = p.getSpecialAttackByID(specID)
       if monster.overrideSpecialChances ~= nil then
       if monster.overrideSpecialChances ~= nil then
         normalChance = normalChance - monster.overrideSpecialChances[i]
         normalChance = normalChance - monster.overrideSpecialChances[i]
       else
       else
         normalChance = normalChance - specAttack.chance
         normalChance = normalChance - specAttack.defaultChance
       end
       end
       local thisMax = 0
       local thisMax = p.getSpecAttackMaxHit(specAttack, normalMaxHit)
      if specAttack.setDamage ~= nil then
       if p.canSpecAttackApplyEffect(specAttack, 'Stun') then canStun = true end
        thisMax = specAttack.setDamage * 10
       if p.canSpecAttackApplyEffect(specAttack, 'Sleep') then canSleep = true end
      else
        thisMax = normalMaxHit
      end
       if specAttack.canStun ~= nil and specAttack.canStun then canStun = true end
       if specAttack.canSleep ~= nil and specAttack.canSleep then canSleep = true end


       if thisMax > specialMaxHit then specialMaxHit = thisMax end
       if thisMax > specialMaxHit then specialMaxHit = thisMax end
       if specAttack.activeBuffs and specAttack.activeBuffTurns ~= nil and specAttack.activeBuffTurns > 0 then  
       if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then  
         hasActiveBuffSpec = true  
         hasActiveBuffSpec = true  
       end
       end
     end
     end
    mw.log(canStun)


    if canSleep and doStuns then damageMultiplier = damageMultiplier * 1.2 end
     if canStun and doStuns then damageMultiplier = damageMultiplier * 1.3 end
     if canStun and doStuns then damageMultiplier = damageMultiplier * 1.3 end
    if canSleep and doStuns then damageMultiplier = damageMultiplier * 1.2 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
Line 356: Line 400:


function p._getMonsterBaseMaxHit(monster)
function p._getMonsterBaseMaxHit(monster)
   local effStrLvl = 0
   --8/27/21 - Now references p.calculateStandardMaxHit for Melee & Ranged
   local strBonus = 0
   local result = 0
   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
   if monster.attackType == 'melee' then
     effStrLvl = monster.strengthLevel + 9
     local baseLevel = p.getMonsterLevel(monster, 'Strength')
     strBonus = monster.strengthBonus
    local bonus = p.getEquipmentStat(monster, 'meleeStrengthBonus')
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     result = p.calculateStandardMaxHit(baseLevel, bonus)
     effStrLvl = monster.rangedLevel + 9
   elseif monster.attackType == 'ranged' then
     strBonus = monster.strengthBonusRanged
     local baseLevel = p.getMonsterLevel(monster, 'Ranged')
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
    local bonus = p.getEquipmentStat(monster, 'rangedStrengthBonus')
     result = p.calculateStandardMaxHit(baseLevel, bonus)
   elseif monster.attackType == 'magic' then
     local mSpell = nil
     local mSpell = nil
     if monster.selectedSpell ~= nil then mSpell = Magic.getSpellByID('Spells', monster.selectedSpell) end
     if monster.selectedSpell ~= nil then mSpell = Magic.getSpellByID('Spells', monster.selectedSpell) end
     if mSpell == nil then
     
      return math.floor(10 * (monster.setMaxHit + (monster.setMaxHit * monster.damageBonusMagic / 100)))
     local bonus = p.getEquipmentStat(monster, 'magicDamageBonus')
     else
    local baseLevel = p.getMonsterLevel(monster, 'Magic')
      return math.floor(10 * (mSpell.maxHit + (mSpell.maxHit * monster.damageBonusMagic / 100)))
 
    end
     result = math.floor(10 * mSpell.maxHit * (1 + bonus / 100) * (1 + (baseLevel + 1) / 200))
   else
   else
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
   end
   end


  --Should only get here for Melee/Ranged, which use functionally the same damage formula
   return result
   return math.floor(10 * (1.3 + (effStrLvl/10) + (strBonus / 80) + ((effStrLvl * strBonus) / 640)))
end
end


Line 403: Line 448:
   local iconText = ''
   local iconText = ''
   local typeText = ''
   local typeText = ''
   if  Constants.getCombatStyleName(monster.attackType) == 'Melee' then
   if  monster.attackType == 'melee' then
     iconText = Icons.Icon({'Melee', notext=true})
     iconText = Icons.Icon({'Melee', notext=true})
     typeText = 'Melee'
     typeText = 'Melee'
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
   elseif monster.attackType == 'ranged' then
     iconText = Icons.Icon({'Ranged', type='skill', notext=true})
     iconText = Icons.Icon({'Ranged', type='skill', notext=true})
     typeText = 'Ranged'
     typeText = 'Ranged'
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
   elseif monster.attackType == 'magic' then
     iconText = Icons.Icon({'Magic', type='skill', notext=true})
     iconText = Icons.Icon({'Magic', type='skill', notext=true})
     typeText = 'Magic'
     typeText = 'Magic'
Line 418: Line 463:


   local normalAttackChance = 100
   local normalAttackChance = 100
   if monster.hasSpecialAttack then
   if monster.specialAttacks[1] ~= nil then
     for i, specID in pairs(monster.specialAttackID) do
     for i, specAttack in pairs(monster.specialAttacks) do
      local specAttack = p.getSpecialAttackByID(specID)
       local attChance = 0
       local attChance = 0
       if monster.overrideSpecialChances ~= nil then
       if monster.overrideSpecialChances ~= nil then
         attChance = monster.overrideSpecialChances[i]
         attChance = monster.overrideSpecialChances[i]
       else
       else
         attChance = specAttack.chance
         attChance = specAttack.defaultChance
       end
       end
       normalAttackChance = normalAttackChance - attChance
       normalAttackChance = normalAttackChance - attChance
Line 431: Line 475:
       result = result..'\r\n* '..attChance..'% '..iconText..' '..specAttack.name..'\r\n** '..specAttack.description
       result = result..'\r\n* '..attChance..'% '..iconText..' '..specAttack.name..'\r\n** '..specAttack.description


       if specAttack.activeBuffs and specAttack.activeBuffTurns ~= nil and specAttack.activeBuffTurns > 0 then
       if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then
         table.insert(buffAttacks, specAttack.name)
         table.insert(buffAttacks, specAttack.name)
         hasActiveBuffSpec = true  
         hasActiveBuffSpec = true  
Line 444: Line 488:
     --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)
     result = '* '..iconText..' 1 - '..p._getMonsterBaseMaxHit(monster)..' '..typeText..' Damage (Instead of repeating '..table.concat(buffAttacks, ' or ')..' while the buff is already active)'..result
     result = '* '..iconText..' 1 - '..p._getMonsterBaseMaxHit(monster)..' '..typeText..' Damage (Instead of repeating '..table.concat(buffAttacks, ' or ')..' while the effect is already active)'..result
   end
   end


Line 485: Line 529:
   local result = '[[Category:Monsters]]'
   local result = '[[Category:Monsters]]'


   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
   if monster.attackType == 'melee' then
     result = result..'[[Category:Melee Monsters]]'
     result = result..'[[Category:Melee Monsters]]'
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
   elseif monster.attackType == 'ranged' then
     result = result..'[[Category:Ranged Monsters]]'
     result = result..'[[Category:Ranged Monsters]]'
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
   elseif monster.attackType == 'magic' then
     result = result..'[[Category:Magic Monsters]]'
     result = result..'[[Category:Magic Monsters]]'
   end
   end


   if monster.hasSpecialAttack then
   if monster.specialAttacks[1] ~= nil then
     result = result..'[[Category:Monsters with Special Attacks]]'
     result = result..'[[Category:Monsters with Special Attacks]]'
   end
   end