Module:Monster

From CrawlWiki
Revision as of 04:23, 15 December 2015 by Edsrzf (talk | contribs) (Improve monsters_with_spell)
Jump to: navigation, search

Documentation for this module may be created at Module:Monster/doc

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, "<br>")

  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, "<br>")
  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, "<br>")
  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, "<br>")

  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", "<br>")
  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[[Category:Spellcaster]]"
  local cleared = false
  for i, spells in ipairs(monster.Spellsets) do
    ret = ret .. [=[<div style="margin: 1.0em; margin-top:0; margin-right:0; padding: 0px; float: left; border:none;">
{| class="prettytable" style="border:none; margin:0; padding:0; width:16em;"
! colspan="3" style="font-size:larger;" | Spell set ]=] .. roman[i] .. "\n"
    for j, spell in ipairs(spells) do
      ret = ret .. '|-\n! align="left" | Slot<sup>' .. j .. '</sup>\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|}</div>"
    cleared = i % 2 == 0
    if cleared then
      ret = ret .. '<div style="clear:both;"></div>'
    end
  end
  if not cleared then
    ret = ret .. '<div style="clear:both;"></div>'
  end
  return ret
end

-- returns:
-- * 0 if monster doesn't have spell
-- * 1 if monster might have spell, depending on spell set
-- * 2 if monster always has spell
local function monster_has_spell(monster, spell_name)
  if not monster.Spellsets then
    return 0
  end

  local has = false
  local all = true
  for _, spellset in ipairs(monster.Spellsets) do
    local contains = false
    for _, spell in ipairs(spellset) do
      if spell.Spell == spell_name then
        contains = true
        break
      end
    end
    has = has or contains
    all = all and contains
  end
  if has then
    if all then
      return 2
    end
    return 1
  end
  return 0
end

local function string_icmp(s1, s2)
  return s1:lower() < s2:lower()
end

local disambig = {
  centaur = "Centaur (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)",
}

function p.monsters_with_spell(frame)
  local spell_name = name_arg(frame)
  local always = {}
  local sometimes = {}
  for monster, monsterdata in pairs(data) do
    local has_spell = monster_has_spell(monsterdata, spell_name)
    if has_spell > 0 then
      monster = disambig[monster] or (monster:gsub("^%l", string.upper))
      if has_spell == 1 then
        table.insert(sometimes, monster)
      elseif has_spell == 2 then
        table.insert(always, monster)
      end
    end
  end
  table.sort(always, string_icmp)
  table.sort(sometimes, string_icmp)
  local ret = {}
  if #always > 0 then
    table.insert(ret, "The following enemies cast " .. spell_name .. ":")
    for _, monster in ipairs(always) do
      table.insert(ret,  "* " .. frame:expandTemplate{title = "monsterlink", args = {monster}})
    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 _, monster in ipairs(sometimes) do
      table.insert(ret,  "* " .. frame:expandTemplate{title = "monsterlink", args = {monster}})
    end
  end
  return table.concat(ret, "\n")
end

return p