Jump to content
Toggle menu
  • 3.1K articles
  • 4.4K files
  • 18 users
  • 91.5K edits
Ashenzari's Archive
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Monster: Difference between revisions

From Ashenzari's Archive
imported>Edsrzf
Death knight needs disambiguation
imported>Edsrzf
Monsters with spell includes damage. Also some refactoring
Line 165: Line 165:


local lich_spells = {
local lich_spells = {
   ["Corrosive Bolt"] = 101,
   ["Corrosive Bolt"] = true,
   ["Lehudib's Crystal Spear"] = 101,
   ["Lehudib's Crystal Spear"] = true,
   ["Orb of Destruction"] = 101,
   ["Orb of Destruction"] = true,
   ["Summon Greater Demon"] = 50,
   ["Summon Greater Demon"] = 50,
   ["Banishment"] = 33.3,
   ["Banishment"] = 33.3,
   ["Haste"] = 33.3,
   ["Haste"] = 33.3,
   ["Invisibility"] = 33.3,
   ["Invisibility"] = 33.3,
   ["Agony"] = 101,
   ["Agony"] = true,
   ["Bolt of Cold"] = 101,
   ["Bolt of Cold"] = true,
   ["Bolt of Draining"] = 101,
   ["Bolt of Draining"] = true,
   ["Bolt of Fire"] = 101,
   ["Bolt of Fire"] = true,
   ["Confuse"] = 101,
   ["Confuse"] = true,
   ["Fireball"] = 101,
   ["Fireball"] = true,
   ["Haunt"] = 101,
   ["Haunt"] = true,
   ["Iron Shot"] = 101,
   ["Iron Shot"] = true,
   ["Iskenderun's Mystic Blast"] = 101,
   ["Iskenderun's Mystic Blast"] = true,
   ["Iskenderun's Battlesphere"] = 101,
   ["Iskenderun's Battlesphere"] = true,
   ["Lee's Rapid Deconstruction"] = 101,
   ["Lee's Rapid Deconstruction"] = true,
   ["Lightning Bolt"] = 101,
   ["Lightning Bolt"] = true,
   ["Malign Gateway"] = 101,
   ["Malign Gateway"] = true,
   ["Paralyse"] = 101,
   ["Paralyse"] = true,
   ["Petrify"] = 101,
   ["Petrify"] = true,
   ["Poison Arrow"] = 101,
   ["Poison Arrow"] = true,
   ["Spellforged Servitor"] = 101,
   ["Spellforged Servitor"] = true,
   ["Simulacrum"] = 101,
   ["Simulacrum"] = true,
   ["Sleep"] = 101,
   ["Sleep"] = true,
   ["Slow"] = 101,
   ["Slow"] = true,
   ["Summon Horrible Things"] = 101,
   ["Summon Horrible Things"] = true,
   ["Throw Icicle"] = 101,
   ["Throw Icicle"] = true,
}
}


local panlord_spells = {
local panlord_spells = {
   ["Fire Storm"] = 101,
   ["Fire Storm"] = true,
   Glaciate = 101,
   Glaciate = true,
   ["Lehudib's Crystal Spear"] = 101,
   ["Lehudib's Crystal Spear"] = true,
   ["Chain Lightning"] = 101,
   ["Chain Lightning"] = true,
   ["Orb of Destruction"] = 101,
   ["Orb of Destruction"] = true,
   ["Corrosive Bolt"] = 101,
   ["Corrosive Bolt"] = true,
   ["Disintegrate"] = 101,
   ["Disintegrate"] = true,
   ["Bolt of Fire"] = 101,
   ["Bolt of Fire"] = true,
   ["Bolt of Cold"] = 101,
   ["Bolt of Cold"] = true,
   ["Iron Shot"] = 101,
   ["Iron Shot"] = true,
   ["Poison Arrow"] = 101,
   ["Poison Arrow"] = true,
   ["Bolt of Draining"] = 101,
   ["Bolt of Draining"] = true,
   ["Quicksilver Bolt"] = 101,
   ["Quicksilver Bolt"] = true,
   ["Force Lance"] = 101,
   ["Force Lance"] = true,
   Fireball = 101,
   Fireball = true,
   ["Bolt of Magma"] = 101,
   ["Bolt of Magma"] = true,
   ["Lee's Rapid Deconstruction"] = 101,
   ["Lee's Rapid Deconstruction"] = true,
   ["Lightning Bolt"] = 101,
   ["Lightning Bolt"] = true,
   Blinkbolt = 101,
   Blinkbolt = true,
   ["Venom Bolt"] = 101,
   ["Venom Bolt"] = true,
   Agony = 101,
   Agony = true,
   Sleep = 101,
   Sleep = true,
   ["Iskendrun's Mystic Blast"] = 101,
   ["Iskendrun's Mystic Blast"] = true,
   ["Sticky Flame Range"] = 101,
   ["Sticky Flame Range"] = true,
   ["Steam Ball"] = 101,
   ["Steam Ball"] = true,
   ["Throw Icicle"] = 101,
   ["Throw Icicle"] = true,
   Airstrike = 101,
   Airstrike = true,
   Smiting = 101,
   Smiting = true,
   ["Dazzling Spray"] = 101,
   ["Dazzling Spray"] = true,
   ["Stone Arrow"] = 101,
   ["Stone Arrow"] = true,
   ["Static Discharge"] = 101,
   ["Static Discharge"] = true,
   ["Vampiric Draining"] = 101,
   ["Vampiric Draining"] = true,
   ["Throw Flame"] = 101,
   ["Throw Flame"] = true,
   ["Throw Frost"] = 101,
   ["Throw Frost"] = true,
   ["Summon Demon"] = 101,
   ["Summon Demon"] = true,
   ["Summon Dragon"] = 101,
   ["Summon Dragon"] = true,
   ["Summon Horrible Things"] = 101,
   ["Summon Horrible Things"] = true,
   ["Summon Greater Demon"] = 101,
   ["Summon Greater Demon"] = true,
   Haunt = 101,
   Haunt = true,
   ["Summon Hydra"] = 101,
   ["Summon Hydra"] = true,
   ["Malign Gateway"] = 101,
   ["Malign Gateway"] = true,
   Haste = 101,
   Haste = true,
   Invisibility = 101,
   Invisibility = true,
   ["Symbol of Torment"] = 101,
   ["Symbol of Torment"] = true,
   ["Monstrous Menagerie"] = 101,
   ["Monstrous Menagerie"] = true,
   Silence = 101,
   Silence = true,
   ["Shadow Creatures"] = 101,
   ["Shadow Creatures"] = true,
   ["Summon Vermin"] = 101,
   ["Summon Vermin"] = true,
   ["Summon Swarm"] = 101,
   ["Summon Swarm"] = true,
   ["Iskenderun's Battlesphere"] = 101,
   ["Iskenderun's Battlesphere"] = true,
   ["Fulminant Prism"] = 101,
   ["Fulminant Prism"] = true,
   ["Summon Ice Beast"] = 101,
   ["Summon Ice Beast"] = true,
   Swiftness = 101,
   Swiftness = true,
   Blink = 101,
   Blink = true,
   ["Summon Butterflies"] = 101,
   ["Summon Butterflies"] = true,
   Shatter = 101,
   Shatter = true,
   Banishment = 101,
   Banishment = true,
   ["Freezing Cloud"] = 101,
   ["Freezing Cloud"] = true,
   ["Poisonous Cloud"] = 101,
   ["Poisonous Cloud"] = true,
   ["Mass Confusion"] = 101,
   ["Mass Confusion"] = true,
   ["Metabolic Englaciation"] = 101,
   ["Metabolic Englaciation"] = true,
   ["Dispel Undead"] = 101,
   ["Dispel Undead"] = true,
   Dig = 101,
   Dig = true,
   Petrify = 101,
   Petrify = true,
   ["Olgreb's Toxic Radiance"] = 101,
   ["Olgreb's Toxic Radiance"] = true,
   Paralyse = 101,
   Paralyse = true,
   Polymorph = 101,
   Polymorph = true,
   ["Mephitic Cloud"] = 101,
   ["Mephitic Cloud"] = true,
   Confuse = 101,
   Confuse = true,
   ["Teleport Other"] = 101,
   ["Teleport Other"] = true,
   Slow = 101,
   Slow = true,
   ["Hellfire Burst"] = 101,
   ["Hellfire Burst"] = true,
   ["Metal Splinters"] = 101,
   ["Metal Splinters"] = true,
   ["Energy Bolt"] = 101,
   ["Energy Bolt"] = true,
   ["Orb of Electricity"] = 101,
   ["Orb of Electricity"] = true,
   ["Hellfire"] = 101,
   ["Hellfire"] = true,
   ["Summon Eyeballs"] = 10,
   ["Summon Eyeballs"] = 10,
   ["Summon Greater Demon"] = 101
   ["Summon Greater Demon"] = true,
}
}


Line 286: Line 286:
end
end


-- returns a number between 0 and 100: the percentage chance that a monster will have the spell
-- If a monster may be able to cast the spell, returns a table. Otherwise returns nil.
-- Monsters with no spellsets that contain the spell return 0.
-- The table contains the following keys:
-- Monsters with all spellsets containing the spell return 100.
-- * chance - the chance that the monster will have the spell. May be missing if the chance is too hard to calculate.
-- Monsters that might have the spell, but it's too hard to calculate the percentage chance, return 101.
-- * damage - the damage that the spell does when the monster casts it (if any)
local function monster_has_spell(monster, spell_name)
local function monster_spell_data(monster, spell_name)
  local special_table = nil
   if monster.Species == "lich" and monster.Name ~= "Boris" then
   if monster.Species == "lich" and monster.Name ~= "Boris" then
     return lich_spells[spell_name] or 0
     special_table = lich_spells
  elseif monster.Name == "pandemonium lord" then
    special_table = panlord_spells
   end
   end


   if monster.Name == "pandemonium lord" then
   if special_table then
     return panlord_spells[spell_name] or 0
    local chance = special_table[spell_name]
    if chance then
      if type(chance) == "number" then
        return {chance = chance}
      else
        return {}
      end
     else
      return nil
    end
   end
   end


   if not monster.Spellsets then
   if not monster.Spellsets then
     return 0
     return nil
   end
   end


   local has = 0
   local has = 0
   local total = 0
   local total = 0
  local damage = nil
   for _, spellset in ipairs(monster.Spellsets) do
   for _, spellset in ipairs(monster.Spellsets) do
     total = total + 1
     total = total + 1
Line 310: Line 323:
       if spell.Spell == spell_name then
       if spell.Spell == spell_name then
         has = has + 1
         has = has + 1
        if not damage then
          damage = spell.Damage
        end
         break
         break
       end
       end
     end
     end
   end
   end
   return has * 100 / total
   if has > 0 then
    return {chance = has * 100 / total, damage = damage}
  else
    return nil
  end
end
end


Line 348: Line 368:
   wizard = "Wizard (monster)",
   wizard = "Wizard (monster)",
}
}
local function monsterlink(frame, data)
  local ret =  "* " .. frame:expandTemplate{title = "monsterlink", args = {data.monster}}
  if data.damage or data.chance and data.chance ~= 100 then
    ret = ret .. " ("
    if data.damage then
      ret = ret .. data.damage .. " damage"
      if data.chance and data.chance ~= 100 then
        ret = ret .. ", "
      end
    end
    if data.chance and data.chance ~= 100 then
      ret = ret .. ("%.1f%% chance"):format(data.chance)
    end
    ret = ret .. ")"
  end
  return ret
end


function p.monsters_with_spell(frame)
function p.monsters_with_spell(frame)
Line 354: Line 392:
   local sometimes = {}
   local sometimes = {}
   for monster, monsterdata in pairs(data) do
   for monster, monsterdata in pairs(data) do
     local spell_chance = monster_has_spell(monsterdata, spell_name)
     local data = not is_vault_only(monsterdata) and monster_spell_data(monsterdata, spell_name)
     if spell_chance > 0 and not is_vault_only(monsterdata) then
     if data then
       monster = disambig[monster] or (monster:gsub("^%l", string.upper))
       data.monster = disambig[monster] or (monster:gsub("^%l", string.upper))
       if spell_chance == 100 then
       if data.chance and data.chance == 100 then
         table.insert(always, monster)
         table.insert(always, data)
       else
       else
         table.insert(sometimes, {monster, spell_chance})
         table.insert(sometimes, data)
       end
       end
     end
     end
   end
   end
   table.sort(always, string_icmp)
   table.sort(always, function (m1, m2) return string_icmp(m1.monster, m2.monster) end)
   table.sort(sometimes, function(m1, m2) return string_icmp(m1[1], m2[1]) end)
   table.sort(sometimes, function(m1, m2) return string_icmp(m1.monster, m2.monster) end)
   local ret = {}
   local ret = {}
   if #always > 0 then
   if #always > 0 then
     table.insert(ret, "The following enemies cast " .. spell_name .. ":")
     table.insert(ret, "The following enemies cast " .. spell_name .. ":")
     for _, monster in ipairs(always) do
     for _, data in ipairs(always) do
       table.insert(ret, "* " .. frame:expandTemplate{title = "monsterlink", args = {monster}})
       table.insert(ret, monsterlink(frame, data))
     end
     end
   end
   end
   if #sometimes > 0 then
   if #sometimes > 0 then
     table.insert(ret, "The following enemies may be able to cast " .. spell_name .. ", depending on their spell set:")
     table.insert(ret, "The following enemies may be able to cast " .. spell_name .. ", depending on their spell set:")
     for _, monster in ipairs(sometimes) do
     for _, data in ipairs(sometimes) do
      local name = monster[1]
       table.insert(ret, monsterlink(frame, data))
      local chance = monster[2]
      local bullet = "* " .. frame:expandTemplate{title = "monsterlink", args = {name}}
      if chance ~= 101 then
        bullet = bullet .. (" (%.1f%% chance)"):format(monster[2])
      end
       table.insert(ret, bullet)
     end
     end
   end
   end

Revision as of 21:54, 6 January 2016

local p = {} local data = mw.loadData('Module: Table of monsters')

-- Taken from max_corpse_chunks in mon-util.cc local max_corpse_chunks = {

 Tiny = 1,
 Little = 2,
 Small = 3,
 Medium = 4,
 Large = 9,
 Big = 10,
 Giant = 12,

}

local function name_arg(frame)

 local name = frame.args[1]
 if not name or name == "" then
   name = mw.title.getCurrentTitle().text
 end
 return name

end

function p.glyph(frame)

 local name = name_arg(frame)
 local monster = data[name] or data[name:lower()]
 if monster then
   return frame:expandTemplate{title = monster.Colour, args = {monster.Glyph}}
 else
   -- probably obsolete monster; fall back to old templates
   return frame:expandTemplate{title = "glyph/" .. name}
 end

end

function p.monster_info(frame)

 local name = name_arg(frame)
 local monster = data[name] or data[name:lower()]
 if not monster then
   return name
 end
 local args = {}
 args.name = monster.Name
 args.glyph = frame:expandTemplate{title = monster.Colour, args = {monster.Glyph}}
 local flags = {}
 for i, v in ipairs(monster.Flags) do
   flags[i] = frame:expandTemplate{title = v}
 end
 args.flags = table.concat(flags, "
")
 local resistances = {}
 for i, v in ipairs(monster.Resistances) do
   resistances[i] = frame:expandTemplate{title = v}
 end
 if #resistances == 0 then
   args.resistances = "None"
 else
   args.resistances = table.concat(resistances, "
") end
 local vulnerabilities = {}
 for i, v in ipairs(monster.Vulnerabilities) do
   vulnerabilities[i] = frame:expandTemplate{title = v}
 end
 if #vulnerabilities == 0 then
   args.vulnerabilities = "None"
 else
   args.vulnerabilities = table.concat(vulnerabilities, "
") end
 if monster.Corpse ~= "No" then
   args.max_chunks = max_corpse_chunks[monster.Size] or 0
 else
   args.max_chunks = 0
 end
 args.meat = frame:expandTemplate{title = monster.Corpse .. " corpse"}
 args.xp = monster.XP
 args.holiness = frame:expandTemplate{title = monster.Holiness}
 args.magic_resistance = monster.MR
 local hp_min = monster.HD*monster["Base HP"] + monster["Fixed HP"]
 local hp_max = monster.HD*(monster["Base HP"]+monster["Rand HP"])+monster["Fixed HP"]
 if hp_min == hp_max then
   args.hp_range = hp_min
 else
   args.hp_range = ("%d-%d"):format(hp_min, hp_max)
 end
 args.avg_hp = (hp_min + hp_max)/2
 args.armour_class = monster.AC
 args.evasion = monster.EV
 args.habitat = monster.Habitat
 args.speed = monster.Speed
 args.size = frame:expandTemplate{title = monster.Size}
 local item_use = {}
 for i, v in ipairs(monster["Item Use"]) do
   item_use[i] = frame:expandTemplate{title = v}
 end
 args.item_use = table.concat(item_use, "
")
 for i = 1, 4 do
   local attack = monster.Attacks[i]
   if attack then
     local typ = frame:expandTemplate{title = attack.Type .. " type"}
     local flavour = frame:expandTemplate{title = attack.Flavour .. " flavour"}
     args["attack" .. i] = ("%d (%s: %s)"):format(attack.Damage, typ, flavour)
   else
     args["attack" .. i] = ""
   end
 end
 args.hit_dice = monster.HD
 args.base_hp = monster["Base HP"]
 args.extra_hp = monster["Rand HP"]
 args.fixed_hp = monster["Fixed HP"]
 args.intelligence = frame:expandTemplate{title = monster.Intelligence .. " intelligence"}
 args.genus = monster.Genus
 args.species = monster.Species
 local infobox = frame:expandTemplate{title = "monster", args = args}
 local flavour = monster.Description
 if monster.Quote then
   flavour = flavour .. "\n----\n" .. monster.Quote:gsub("\n", "
") end flavour = frame:expandTemplate{title = "flavour", args = {flavour}} return infobox .. "\n" .. flavour

end

local roman = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"}

function p.monster_spells(frame)

 local name = name_arg(frame)
 local monster = data[name] or data[name:lower()]
 local ret = "==Spells==\n"
 local cleared = false
 for i, spells in ipairs(monster.Spellsets) do

ret = ret .. [=[

Spell set ]=] .. roman[i] .. "\n"
   for j, spell in ipairs(spells) do
     ret = ret .. '|-\n! align="left" | Slot' .. j .. '\n| ' .. spell.Spell .. ''
     if spell.Damage then
       ret = ret .. " (" .. spell.Damage .. ")"
     end
     ret = ret .. '\n| '
     local flags = {}
     for i, v in ipairs(spell.Flags) do
       flags[i] = frame:expandTemplate{title = v .. " slot flag"}
     end
     ret = ret .. table.concat(flags, ", ") .. "\n"
   end
ret = ret .. "|-\n|}"
   cleared = i % 2 == 0
   if cleared then
ret = ret .. '
'
   end
 end
 if not cleared then
ret = ret .. '
'
 end
 return ret

end

local lich_spells = {

 ["Corrosive Bolt"] = true,
 ["Lehudib's Crystal Spear"] = true,
 ["Orb of Destruction"] = true,
 ["Summon Greater Demon"] = 50,
 ["Banishment"] = 33.3,
 ["Haste"] = 33.3,
 ["Invisibility"] = 33.3,
 ["Agony"] = true,
 ["Bolt of Cold"] = true,
 ["Bolt of Draining"] = true,
 ["Bolt of Fire"] = true,
 ["Confuse"] = true,
 ["Fireball"] = true,
 ["Haunt"] = true,
 ["Iron Shot"] = true,
 ["Iskenderun's Mystic Blast"] = true,
 ["Iskenderun's Battlesphere"] = true,
 ["Lee's Rapid Deconstruction"] = true,
 ["Lightning Bolt"] = true,
 ["Malign Gateway"] = true,
 ["Paralyse"] = true,
 ["Petrify"] = true,
 ["Poison Arrow"] = true,
 ["Spellforged Servitor"] = true,
 ["Simulacrum"] = true,
 ["Sleep"] = true,
 ["Slow"] = true,
 ["Summon Horrible Things"] = true,
 ["Throw Icicle"] = true,

}

local panlord_spells = {

 ["Fire Storm"] = true,
 Glaciate = true,
 ["Lehudib's Crystal Spear"] = true,
 ["Chain Lightning"] = true,
 ["Orb of Destruction"] = true,
 ["Corrosive Bolt"] = true,
 ["Disintegrate"] = true,
 ["Bolt of Fire"] = true,
 ["Bolt of Cold"] = true,
 ["Iron Shot"] = true,
 ["Poison Arrow"] = true,
 ["Bolt of Draining"] = true,
 ["Quicksilver Bolt"] = true,
 ["Force Lance"] = true,
 Fireball = true,
 ["Bolt of Magma"] = true,
 ["Lee's Rapid Deconstruction"] = true,
 ["Lightning Bolt"] = true,
 Blinkbolt = true,
 ["Venom Bolt"] = true,
 Agony = true,
 Sleep = true,
 ["Iskendrun's Mystic Blast"] = true,
 ["Sticky Flame Range"] = true,
 ["Steam Ball"] = true,
 ["Throw Icicle"] = true,
 Airstrike = true,
 Smiting = true,
 ["Dazzling Spray"] = true,
 ["Stone Arrow"] = true,
 ["Static Discharge"] = true,
 ["Vampiric Draining"] = true,
 ["Throw Flame"] = true,
 ["Throw Frost"] = true,
 ["Summon Demon"] = true,
 ["Summon Dragon"] = true,
 ["Summon Horrible Things"] = true,
 ["Summon Greater Demon"] = true,
 Haunt = true,
 ["Summon Hydra"] = true,
 ["Malign Gateway"] = true,
 Haste = true,
 Invisibility = true,
 ["Symbol of Torment"] = true,
 ["Monstrous Menagerie"] = true,
 Silence = true,
 ["Shadow Creatures"] = true,
 ["Summon Vermin"] = true,
 ["Summon Swarm"] = true,
 ["Iskenderun's Battlesphere"] = true,
 ["Fulminant Prism"] = true,
 ["Summon Ice Beast"] = true,
 Swiftness = true,
 Blink = true,
 ["Summon Butterflies"] = true,
 Shatter = true,
 Banishment = true,
 ["Freezing Cloud"] = true,
 ["Poisonous Cloud"] = true,
 ["Mass Confusion"] = true,
 ["Metabolic Englaciation"] = true,
 ["Dispel Undead"] = true,
 Dig = true,
 Petrify = true,
 ["Olgreb's Toxic Radiance"] = true,
 Paralyse = true,
 Polymorph = true,
 ["Mephitic Cloud"] = true,
 Confuse = true,
 ["Teleport Other"] = true,
 Slow = true,
 ["Hellfire Burst"] = true,
 ["Metal Splinters"] = true,
 ["Energy Bolt"] = true,
 ["Orb of Electricity"] = true,
 ["Hellfire"] = true,
 ["Summon Eyeballs"] = 10,
 ["Summon Greater Demon"] = true,

}

local function is_vault_only(monster)

 for _, flag in ipairs(monster.Flags) do
   if flag == "Vault flag" then
     return true
   end
 end
 return false

end

-- If a monster may be able to cast the spell, returns a table. Otherwise returns nil. -- The table contains the following keys: -- * chance - the chance that the monster will have the spell. May be missing if the chance is too hard to calculate. -- * damage - the damage that the spell does when the monster casts it (if any) local function monster_spell_data(monster, spell_name)

 local special_table = nil
 if monster.Species == "lich" and monster.Name ~= "Boris" then
   special_table = lich_spells
 elseif monster.Name == "pandemonium lord" then
   special_table = panlord_spells
 end
 if special_table then
   local chance = special_table[spell_name]
   if chance then
     if type(chance) == "number" then
       return {chance = chance}
     else
       return {}
     end
   else
     return nil
   end
 end
 if not monster.Spellsets then
   return nil
 end
 local has = 0
 local total = 0
 local damage = nil
 for _, spellset in ipairs(monster.Spellsets) do
   total = total + 1
   for _, spell in ipairs(spellset) do
     if spell.Spell == spell_name then
       has = has + 1
       if not damage then
         damage = spell.Damage
       end
       break
     end
   end
 end
 if has > 0 then
   return {chance = has * 100 / total, damage = damage}
 else
   return nil
 end

end

local function string_icmp(s1, s2)

 return s1:lower() < s2:lower()

end

local disambig = {

 centaur = "Centaur (monster)",
 ["death knight"] = "Death knight (monster)",
 ["deep dwarf"] = "Deep dwarf (monster)",
 demigod = "Demigod (monster)",
 demonspawn = "Demonspawn (monster)",
 draconian = "Draconian (monster)",
 felid = "Felid (monster)",
 formicid = "Formicid (monster)",
 gargoyle = "Gargoyle (monster)",
 ghoul = "Ghoul (monster)",
 halfling = "Halfling (monster)",
 human = "Human (monster)",
 kobold = "Kobold (monster)",
 merfolk = "Merfolk (monster)",
 minotaur = "Minotaur (monster)",
 mummy = "Mummy (monster)",
 naga = "Naga (monster)",
 necromancer = "Necromancer (monster)",
 octopode = "Octopode (monster)",
 ogre = "Ogre (monster)",
 spriggan = "Spriggan (monster)",
 troll = "Troll (monster)",
 tengu = "Tengu (monster)",
 vampire = "Vampire (monster)",
 wizard = "Wizard (monster)",

}

local function monsterlink(frame, data)

 local ret =  "* " .. frame:expandTemplate{title = "monsterlink", args = {data.monster}}
 if data.damage or data.chance and data.chance ~= 100 then
   ret = ret .. " ("
   if data.damage then
     ret = ret .. data.damage .. " damage"
     if data.chance and data.chance ~= 100 then
       ret = ret .. ", "
     end
   end
   if data.chance and data.chance ~= 100 then
     ret = ret .. ("%.1f%% chance"):format(data.chance)
   end
   ret = ret .. ")"
 end
 return ret

end

function p.monsters_with_spell(frame)

 local spell_name = name_arg(frame)
 local always = {}
 local sometimes = {}
 for monster, monsterdata in pairs(data) do
   local data = not is_vault_only(monsterdata) and monster_spell_data(monsterdata, spell_name)
   if data then
     data.monster = disambig[monster] or (monster:gsub("^%l", string.upper))
     if data.chance and data.chance == 100 then
       table.insert(always, data)
     else
       table.insert(sometimes, data)
     end
   end
 end
 table.sort(always, function (m1, m2) return string_icmp(m1.monster, m2.monster) end)
 table.sort(sometimes, function(m1, m2) return string_icmp(m1.monster, m2.monster) end)
 local ret = {}
 if #always > 0 then
   table.insert(ret, "The following enemies cast " .. spell_name .. ":")
   for _, data in ipairs(always) do
     table.insert(ret, monsterlink(frame, data))
   end
 end
 if #sometimes > 0 then
   table.insert(ret, "The following enemies may be able to cast " .. spell_name .. ", depending on their spell set:")
   for _, data in ipairs(sometimes) do
     table.insert(ret, monsterlink(frame, data))
   end
 end
 return table.concat(ret, "\n")

end

return p