Module:Wand

From Noita Wiki
Jump to navigation Jump to search

This documentation is transcluded from Module:Wand/doc (edit).

This is a template for display of wand builds, and/or detailed information about wand stats. This new template replaces the {{Wand}} and {{Wand Card}} templates. The template output includes a link to view and edit the wand on the Wand Simulator site, which can also export a template of this format for direct insertion into wiki articles.

Key differences to previous version:

  • Spells are now specified using their ID, rather than name.
  • Spells are specified in a comma separated list.

This simplifies exporting/importing Wand builds to mods and other tools.

Some useful resources:

Example 1:
Wand2 basic

This demonstrates leaving gaps in the wand's spell listing. The spells can be formatted any way you want - whitespace is ignored.

{{Wand2
| wandPic     = Wand 0413.png
| tooltips    = Yes
| capacity    = 16
| shuffle     = No
| spellsCast  = 1
| alwaysCasts = HOMING
| spells      = MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,,,
                RECHARGE,RECHARGE,RECHARGE,RECHARGE,,,
                DIVIDE_10,DAMAGE,BUCKSHOT
}}
Result:
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
No
Inventory Icon gun actions per round.png Spells/Cast
1
Inventory Icon fire rate wait.png Cast delay
0.17 s
Inventory Icon gun reload time.png Rechrg. Time
0.48 s
Inventory Icon mana max.png Mana max
900
Inventory Icon mana charge speed.png Mana chg. Spd
700
Inventory Icon gun capacity.png Capacity
16
Inventory Icon spread degrees.png Spread
-2.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Inventory Icon gun permanent actions.pngAlways casts
Homing
Homing
Makes a projectile accelerate towards your foes
Type
Projectile modifier
Mana drain
Homing
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Divide By 10
Divide By 10
Casts the next spell 10 times, but with reduced damage
Type
Other
Mana drain
Divide By 10
Damage Plus
Damage Plus
Increases the damage done by a projectile
Type
Projectile modifier
Mana drain
Damage Plus
Triplicate Bolt
Triplicate Bolt
A formation of three small, fast bolts
Type
Projectile
Mana drain
Triplicate Bolt
Example 2:
Wand2 basic - vertical

This demonstrates leaving gaps in the wand's spell listing. The spells can be formatted any way you want - whitespace is ignored.

{{Wand2
| wandPic     = Wand 0413.png
| vertical    = Yes
| tooltips    = Yes
| capacity    = 16
| shuffle     = No
| spellsCast  = 1
| alwaysCasts = HOMING
| spells      = MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,,,
                RECHARGE,RECHARGE,RECHARGE,RECHARGE,,,
                DIVIDE_10,DAMAGE,BUCKSHOT
}}
Result:
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
No
Inventory Icon gun actions per round.png Spells/Cast
1
Inventory Icon fire rate wait.png Cast delay
0.17 s
Inventory Icon gun reload time.png Rechrg. Time
0.48 s
Inventory Icon mana max.png Mana max
900
Inventory Icon mana charge speed.png Mana chg. Spd
700
Inventory Icon gun capacity.png Capacity
16
Inventory Icon spread degrees.png Spread
-2.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Inventory Icon gun permanent actions.pngAlways casts
Homing
Homing
Makes a projectile accelerate towards your foes
Type
Projectile modifier
Mana drain
Homing
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Divide By 10
Divide By 10
Casts the next spell 10 times, but with reduced damage
Type
Other
Mana drain
Divide By 10
Damage Plus
Damage Plus
Increases the damage done by a projectile
Type
Projectile modifier
Mana drain
Damage Plus
Triplicate Bolt
Triplicate Bolt
A formation of three small, fast bolts
Type
Projectile
Mana drain
Triplicate Bolt
Example 3:
Wand2 card with all options

Also shows use of multiple always cast spells (up to 4 are allowed).

{{Wand2
| vertical     = No
| wandCard     = Yes
| hideLink     = No
| tooltips     = No
| wandName     = Second Wand of Examples
| wandPic      = Wand 0413.png
| shuffle      = No
| spellsCast   = 2
| alwaysCasts  = INFESTATION,HOMING,LASER,DAMAGE
| spells       = MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 DIVIDE_10,
                 DAMAGE,
                 BUCKSHOT
| castDelay    = 0.50
| rechargeTime = 1.00
| manaMax      = 500
| manaCharge   = 500
| capacity     = 24
| spread       = 0
| speed        = 1
}}
Result:
Second Wand of Examples
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
No
Inventory Icon gun actions per round.png Spells/Cast
2
Inventory Icon fire rate wait.png Cast delay
0.50 s
Inventory Icon gun reload time.png Rechrg. Time
1.00 s
Inventory Icon mana max.png Mana max
500
Inventory Icon mana charge speed.png Mana chg. Spd
500
Inventory Icon gun capacity.png Capacity
24
Inventory Icon spread degrees.png Spread
0.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Inventory Icon gun permanent actions.pngAlways casts
Infestation
Homing
Concentrated Light
Damage Plus
Add Mana
Add Mana
Add Mana
Add Mana
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Divide By 10
Damage Plus
Triplicate Bolt
Example 4:
Wand2 card with all options - vertical

Also shows use of multiple always cast spells (up to 4 are allowed).

{{Wand2
| vertical     = Yes
| wandCard     = Yes
| hideLink     = No
| tooltips     = No
| wandName     = Vertical Wand of Examples
| wandPic      = Wand 0413.png
| shuffle      = No
| spellsCast   = 2
| alwaysCasts  = INFESTATION,HOMING,LASER,DAMAGE
| spells       = MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 DIVIDE_10,
                 DAMAGE,
                 BUCKSHOT
| castDelay    = 0.50
| rechargeTime = 1.00
| manaMax      = 500
| manaCharge   = 500
| capacity     = 24
| spread       = 0
| speed        = 1
}}
Result:
Vertical Wand of Examples
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
No
Inventory Icon gun actions per round.png Spells/Cast
2
Inventory Icon fire rate wait.png Cast delay
0.50 s
Inventory Icon gun reload time.png Rechrg. Time
1.00 s
Inventory Icon mana max.png Mana max
500
Inventory Icon mana charge speed.png Mana chg. Spd
500
Inventory Icon gun capacity.png Capacity
24
Inventory Icon spread degrees.png Spread
0.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Inventory Icon gun permanent actions.pngAlways casts
Infestation
Homing
Concentrated Light
Damage Plus
Add Mana
Add Mana
Add Mana
Add Mana
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Divide By 10
Damage Plus
Triplicate Bolt
Example 5:
Wand2 card with all options

Also shows use of multiple always cast spells (up to 4 are allowed).

{{Wand2
| vertical     = No
| wandCard     = Yes
| hideLink     = No
| tooltips     = No
| wandName     = Ranged Wand of Examples
| wandPic      = Wand 0413.png
| shuffle      = No
| spellsCast   = 2
| alwaysCasts  = HOMING,DAMAGE
| spells       = MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 MANA_REDUCE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 RECHARGE,
                 DIVIDE_10,
                 DAMAGE,
                 BUCKSHOT
| castDelay    = 0.50,1.2
| rechargeTime = 1.00,2.00
| manaMax      = 500,7000
| manaCharge   = 5,2000
| capacity     = 2,16
| spread       = -20,80
| speed        = 1,5
}}
Result:
Ranged Wand of Examples
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
No
Inventory Icon gun actions per round.png Spells/Cast
( 2 - 7 )
Inventory Icon fire rate wait.png Cast delay
( 0.50 - 1.20 ) s
Inventory Icon gun reload time.png Rechrg. Time
( 1.00 - 2.00 ) s
Inventory Icon mana max.png Mana max
( 500 - 7000 )
Inventory Icon mana charge speed.png Mana chg. Spd
( 5 - 2000 )
Inventory Icon gun capacity.png Capacity
( 2 - 16 )
Inventory Icon spread degrees.png Spread
( -20.0 - 80.0 ) DEG
Inventory Icon speed multiplier.png Speed
× ( 1.00 - 5.00 )
Example 6:
No tooltips
{{Wand2
| tooltips     = No
| wandPic      = Wand 0413.png
| capacity     = 12
| shuffle      = No
| spellsCast   = 1
| alwaysCasts  = INFESTATION,INFESTATION,INFESTATION,INFESTATION
| spells       = MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,RECHARGE,RECHARGE,RECHARGE,RECHARGE,DIVIDE_10,DAMAGE,BUCKSHOT
}}
Result:
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
Yes
Inventory Icon gun actions per round.png Spells/Cast
1
Inventory Icon fire rate wait.png Cast delay
0.17 s
Inventory Icon gun reload time.png Rechrg. Time
0.48 s
Inventory Icon mana max.png Mana max
900
Inventory Icon mana charge speed.png Mana chg. Spd
700
Inventory Icon gun capacity.png Capacity
12
Inventory Icon spread degrees.png Spread
-2.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Inventory Icon gun permanent actions.pngAlways casts
Infestation
Infestation
Infestation
Infestation
Add Mana
Add Mana
Add Mana
Add Mana
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Divide By 10
Damage Plus
Triplicate Bolt
Example 7:
No always cast
{{Wand2
| tooltips     = Yes
| wandPic      = Wand 0413.png
| capacity     = 12
| shuffle      = No
| spellsCast   = 1
| spells       = MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,MANA_REDUCE,RECHARGE,RECHARGE,RECHARGE,RECHARGE,DIVIDE_10,DAMAGE,BUCKSHOT
}}
Result:
Wand 0413.png
Inventory Icon gun shuffle.png Shuffle
Yes
Inventory Icon gun actions per round.png Spells/Cast
1
Inventory Icon fire rate wait.png Cast delay
0.17 s
Inventory Icon gun reload time.png Rechrg. Time
0.48 s
Inventory Icon mana max.png Mana max
900
Inventory Icon mana charge speed.png Mana chg. Spd
700
Inventory Icon gun capacity.png Capacity
12
Inventory Icon spread degrees.png Spread
-2.0 DEG
Inventory Icon speed multiplier.png Speed
× 1.00
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Add Mana
Add Mana
Immediately adds 30 mana to the wand
Type
Projectile modifier
Mana drain
Add Mana
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Reduce Recharge Time
Reduce Recharge Time
Reduces the time between spellcasts
Type
Projectile modifier
Mana drain
Reduce Recharge Time
Divide By 10
Divide By 10
Casts the next spell 10 times, but with reduced damage
Type
Other
Mana drain
Divide By 10
Damage Plus
Damage Plus
Increases the damage done by a projectile
Type
Projectile modifier
Mana drain
Damage Plus
Triplicate Bolt
Triplicate Bolt
A formation of three small, fast bolts
Type
Projectile
Mana drain
Triplicate Bolt

Parameters

  • All parameters are optional.
  • Parameters without a default value are omitted from the output entirely.
  • Ranges may be specified for values by giving a lower and upper bound separated by a comma, e.g.: manaMax=100,700
Parameter Default Description
Wand configuration
wandName Omitted The name of the wand. If this field is blank, or missing, the wand name is omitted. [notes 1]
wandPicId 0821 Specifies the id of the wand image. [notes 1]
shuffle No Whether or not the wand is a shuffle-type wand. Yes/No or 1/0
spellsCast 1 The number of spells cast per cast.
castDelay 0.00 The cast delay between each spell in the wand (in seconds).
rechargeTime 0.00 The recharge delay between each cast (in seconds).
manaMax 500 The maximum mana capacity of the wand.
manaRecharge 250 The mana recharge rate of the wand (in mana per second).
capacity 1 The capacity of the wand. Spell slots numbered greater than this will not be shown.
spread 0.0 The spread of the wand's casts.
speed 1 The speed multiplier of the wand's casts. [notes 1]
alwaysCasts Omitted Comma separated list of Spell IDs. Up to 4 spells that the wand always casts.
spells Comma separated list of Spell IDs. This is the ordered sequence of spells the wand casts. Empty spell slots are produced by omitting an ID between two commas, e.g. MANA_REDUCE,,BUCKSHOT will leave a space empty between Add Mana and Triplicate Bolt.
Template configuration
hideLink No Set to Yes to hide the Wand Simulator link.
vertical Auto Set to Yes to always display in a vertical (mobile-friendly) layout format (this is also enabled automatically on narrow screens via CSS media query).
Deprecated
wandPic Wand_0821.png Specifies the wand image. Must include the filetype extension, e.g.: Wand handgun.png [notes 1]
  1. 1.0 1.1 1.2 1.3 Editing currently unsupported by Wand Simulator.




local p = {}
local cargo = mw.ext.cargo

-- Scribunto (Lua main): https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual
-- Cargo Lua support: https://www.mediawiki.org/wiki/Extension:Cargo/Other_features#Lua_support

local MAX_ALWAYSCAST = 4
local framesPerSecond = 60
local NNBSP = ' '

-- Trims whitespace off of the left and right sides of a string
local function trim(str)
   return str:gsub("^%s*(.-)%s*$", "%1")
end

-- Basically is null, is empty, or is whitespace
local function is_empty(str)
  return str == nil or type(str) == 'string' and string.len(trim(str)) == 0
end

local function table_empty(tbl)
  return next(tbl) == nil
end

-- If a given string is empty or nil, return nil, otherwise give back the value
local function nil_or_value(str)
  if str == nil or type(str) ~= 'string' or str == '' then
    return nil
  end
  return str
end

local function minAndMax(a, b)
  if a == nil then
    return b, b
  end
  if b == nil then
    return a, a
  end
  return math.min(a, b), math.max(a, b)
end

-- For sim url, convert seconds to frames (@60fps)
local function formatSeconds(seconds)
  return ("%u"):format(tonumber(seconds or 0 * framesPerSecond))
end

local function formatRange(minV, maxV, fSingle, fRange)
  if minV == nil and maxV == nil then
    return '--'
  end
  if minV == maxV or maxV == nil then
    return string.format(fSingle, minV)
  end
  if minV == nil then
    return string.format(fSingle, maxV)
  end
  return string.format(fRange, minV, maxV)
end

local function bothIfDifferent(a, b)
  if a == nil and b == nil then
    return nil
  end
  if a == b or b == nil then
    return a
  end
  if a == nil then
    return b
  end
  return a .. ',' .. b
end

-- Normally you'd do `table.insert(table, item)` to add to the end, but this
-- does not appear to be working in this Lua implementation so here's a workaround.
local function append_table(tbl, data)
  tbl[#tbl + 1] = data
end


-- Split a string by comma, return each item trimmed in a table.
local function split(str, by)
	by = by or ','
	local result = {}
	if str == nil then
		return result
  	end
	local pattern = string.format("([^%s]+)", by)
	for token in string.gmatch(str, pattern) do
		append_table(result, trim(token))
	end
	return result
end

local function map(source, func, ...)
  local result = {}
  for key, value in pairs(source) do
      result[key] = func(value, unpack(arg))
  end
  return result
end

local truthy = {"^1", "^y", "^Y", "^true", "^True"}
local falsey = {"^0", "^n", "^N", "^false", "^False"}
local function seek_truth(predicate, default)
	if not is_empty(predicate) then
		if type(predicate) == 'boolean' then
			return predicate
		end
		if type(predicate) == 'number' then
			return not predicate == 0
		end
		if type(predicate) == 'string' then
		    for _, truth in ipairs(truthy) do
		    	if predicate:find(truth) then
		        	return true
		    	end
		    end
	    	for _, falsehood in ipairs(falsey) do
				if predicate:find(falsehood) then
					return false
				end
	    	end
		end
	end
	return default
end

---------------------------------------------- G(et) ARGuments from frames -----
--- If sep is not nil it specifies the character that delimits multiple values
---
local function garg_base(frame, name, default, sep, parseFunc, ...)
	local args = frame.args
	local parentArgs = frame:getParent().args

	local valFromArgs = nil_or_value(parentArgs[name]) or nil_or_value(args[name])

	if valFromArgs == nil then
		return { default, default }
	end
	if parseFunc == nil or not type(parseFunc) == 'function' then
		return { valFromArgs, valFromArgs }
	end

	if not is_empty(sep) then
		return map(split(valFromArgs, sep), parseFunc, unpack(arg))
	end

	local parsedVal = parseFunc(valFromArgs, unpack(arg))
	return { parsedVal, parsedVal }
end

--  local shuffle              = garg_boo("shuffle",      pArgs, args,  false )

-- GARG for strings
local function garg_str(frame, name, default, sep)
  return unpack(garg_base(frame, name, default, sep))
end

-- GARG for numbers
local function garg_num(frame, name, default, sep)
  return unpack(garg_base(frame, name, default, sep, tonumber, 10))
end

-- GARG for booleans
local function garg_boo(frame, name, default, sep)
  return unpack(garg_base(frame, name, default, sep, seek_truth, default))
end

------------------------------------------------------- Cargo spell lookup -----
-- Remove invalid characters from potential spell ID
-- Valid ones are [A-Za-z1-9_], then uppercased
local function sanitizeSpellId(str)
  if is_empty(str) then
    return nil
  end
  return str:gsub("[^%w_]+", ""):upper()
end

-- Split a string of SPELL_IDS,BY,COMMA preserving holes "A,,B" -> ["A","","B"]
local function splitSpells(str)
	local result = {}
	if is_empty(str) then
		return result
	end
	for token in string.gmatch(str, "([^,]*)[,]?") do
		append_table(result, sanitizeSpellId(token) or '')
	end
	return result
end

-- Check if a split spell list actually contains any spells (or just holes)
local function isEmptySpellList(spellList)
  for _, item in ipairs(spellList) do
    if not is_empty(item) then
      return false
    end
  end
  return true
end

-- Build a simple '(id=X or id=Y or...)' query clause
local function makeQuery(spellList, alwaysCastList)
  local result = {}
  for _, item in ipairs(spellList) do
    if not is_empty(item) then
      append_table(result, string.format("id=\"%s\"", item))
    end
  end
  for _, item in ipairs(alwaysCastList) do
    if not is_empty(item) then
      append_table(result, string.format("id=\"%s\"", item))
    end
  end
  return "(" .. table.concat(result, " " .. "OR" .. " ") .. ")"
end

-- Make a new table indexed by the specified key of each item
-- reindex({ 1:  {id: 'q', },  2:  {id: 'f', },  3:  {id: 'm', }}, 'id')
--      =  {'q': {id: 'q', }, 'f': {id: 'f', }, 'm': {id: 'm', }}
local function reindex(from, key)
  local to = {}
  for _, item in ipairs(from) do
    to[item[key]] = item
  end
  return to
end

-- Look up all the spells on the wand in one query
-- Return them as a lookup table keyed by spell ID
local function doSpellQuery(spellIdList, acIdList)
  if isEmptySpellList(spellIdList) then
    return {}
  end
    -- Perform a single cargo query for all spell details
  local query = {}
  query.where = makeQuery(spellIdList, acIdList)
  query.limit = 200
  if tooltips then
    query.fields = [[
      _pageName,image,name,id,description,type,tags,manaDrain,uses,
      damageProjectile,damageMelee,damageElectric,damageFire,
      damageExplosion,damageIce,damageSlice,damageDrill,
      damageHealing,damageHoly,
      speed,castDelay,rechargeDelay,bounces,effect
    ]]
  else
    query.fields = "_pageName,image,name,id,description,type,tags"
  end
  return reindex(cargo.query("Spells", query.fields, query), 'id')
end

--------------------------------------------------- Spell helper functions -----
local SpellTypeClassMap = {
  ["projectile"] = "spellProjectile",
  ["static projectile"] = "spellStatic",
  ["projectile modifier"] = "spellModifier",
  ["multicast"] = "spellMulticast",
  ["material"] = "spellMaterial",
  ["other"] = "spellOther",
  ["utility"] = "spellUtility",
  ["passive"] = "spellPassive",
}
local function getSpellTypeClass(type)
  return SpellTypeClassMap[string.lower(type)] or "spellUnknown"
end

-------------------------------------------------------------- HTML output -----
local function addSpell(parent, spell, tooltips)
  local card = parent:tag('div'):attr('class', 'wand2-spell')
  if type(spell) == "table" then
    card:tag('div')
      :attr('class',
        string.format('spellBorder spellBackground %s',
          getSpellTypeClass(spell.type)))
        :wikitext(
          string.format('[[File:%s|link=%s|alt=%s|128px]]',
            spell.image, spell._pageName, spell.name))
    if tooltips then
      card:addClass('wand2-spelltip-target')
      local tip = card:tag('div'):attr('class', 'wand2-spelltip')
      tip:tag('div'):attr('class', 'wand2-spelltip-name')
          :wikitext(string.format('%s', spell.name))
      tip:tag('div'):attr('class', 'wand2-spelltip-desc')
          :wikitext(string.format('%s', spell.description))

      tip:tag('div'):attr('class', 'wand2-spelltip-key wand2-spelltip-sub')
          :wikitext(string.format('Type'))
      tip:tag('div'):attr('class', 'wand2-spelltip-value wand2-spelltip-sub')
          :wikitext(string.format('%s', spell.type or ''))
      tip:tag('div'):attr('class', 'wand2-spelltip-key')
          :wikitext(string.format('Mana drain'))
      tip:tag('div'):attr('class', 'wand2-spelltip-value')
          :wikitext(string.format('%s', spell.manaDrain or ''))

      tip:tag('div'):attr('class', 'wand2-spelltip-img')
          :wikitext(string.format(
            '[[File:%s|link=%s|alt=%s|128px]]',
              spell.image, spell._pageName, spell.name))
    end
  end
end

---------------------------------------------- Main entry point for module -----
function p.Wand(frame)

	local vertical                       = garg_boo(frame, "vertical",   false )
	local wandCard                       = garg_boo(frame, "wandCard",   false )
	--  Card wraps at 10  vb2x10 +6
	--  Mini wraps at 26     10x 26
	--  Vertical wraps at 6 (6x4 +2)
	local defaultWrap = vertical and 6 or wandCard and 10 or 26
	-- Display options
	local wrapCount                      = garg_num(frame, "wrapCount", defaultWrap )
	local hideLink                       = garg_boo(frame, "hideLink",   false )
	local hideName                       = garg_boo(frame, "hideName",   false )
	local hideSpells                     = garg_boo(frame, "hideSpells", false )
	local tooltips                       = garg_boo(frame, "tooltips",    true )

	-- Wand stats
	-- Always show
	local alwaysCasts                    = garg_str(frame, "alwaysCasts"        )
	local spells                         = garg_str(frame, "spells"             )
	local shuffle                        = garg_boo(frame, "shuffle",     false )
	local pCastMin, pCastMax   = minAndMax(garg_num(frame, "spellsCast",     1,    ','))
	-- Shown on expanded + cardviews
	local delayMin, delayMax   = minAndMax(garg_num(frame, "castDelay",      0.17, ','))
	local rTimeMin, rTimeMax   = minAndMax(garg_num(frame, "rechargeTime",   0.48, ','))
	local manaMin, manaMax     = minAndMax(garg_num(frame, "manaMax",      900,    ','))
	local regenMin, regenMax   = minAndMax(garg_num(frame, "manaCharge",   700,    ','))
	local capMin, capMax       = minAndMax(garg_num(frame, "capacity",      26,    ','))
	local spreadMin, spreadMax = minAndMax(garg_num(frame, "spread",        -2.00, ','))
	-- Shown on expanded + cardviews (Not shown in-game)
	local speedMin, speedMax   = minAndMax(garg_num(frame, "speed",          1.00, ','))
	
	-- Customisation
	local wandName                       = garg_str(frame, "wandName",   "" )
	local wandPic                        = garg_str(frame, "wandPic",    "Wand_0821.png" )
	local wandPicId                      = garg_str(frame, "wandPicId",  "0821" )
	
	local cap = capMax or capMin or 26
	
	-- Constants
	local picSize      = "165px"

  -- Create tables from the CSV strings
  local spellIdList  = splitSpells(spells)
  local acIdList     = splitSpells(alwaysCasts)

  local results = doSpellQuery(spellIdList, acIdList)


  -- Build output HTML
  local root = mw.html.create('div')
    :attr('class', string.format('%s %s',
      wandCard and 'wand2-card' or 'wand2-mini',
      vertical and 'wand2-vertical' or ''))
    :cssText(string.format('--wand2-cap: %d; --wand2-wrap: %d;', cap, wrapCount))


	-- Wand name
	if not hideName and not is_empty(wandName) then
      root:tag('div'):attr('class', 'wand2-name')
      :wikitext(wandName)
	end
 
	-- Wand image
    if not hidePic then
        root:tag('div'):attr('class', 'wand2-sprite')
            :cssText(string.format('max-width: %s;', picSize))
            :wikitext(string.format('[[File:%s|link=]]', wandPic))
    end

    -- Basic wand attributes
	root:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'shuffle')
		:attr('data-value', shuffle and '1' or '0')
    	:tag('div'):attr('class', 'wand2-label')
        	:wikitext('[[File:Inventory Icon gun shuffle.png]] Shuffle')
    	:done()
    	:tag('div'):attr('class', 'wand2-value')
        	:wikitext(string.format('%s', shuffle and 'Yes' or 'No'))

	root:tag('div'):attr('class', 'wand2-stat')
	    :attr('data-name', 'spells-per-cast')
		:attr('data-value-min', pCastMin)
		:attr('data-value-max', pCastMax)
        :tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon gun actions per round.png]] Spells/Cast')
    	:done()
        :tag('div'):attr('class', 'wand2-value')
       		:wikitext(formatRange(pCastMin, pCastMax, '%s', '( %s - %s )'))

	-- Extended wand attributes (initially hidden for mini view)
	local details = root:tag('div'):attr('class', 'wand2-details')

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'cast-delay')
		:attr('data-value-min', delayMin)
		:attr('data-value-max', delayMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon fire rate wait.png]] Cast delay')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(delayMin, delayMax, '%1.2f s', '( %1.2f - %1.2f ) s'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-recharge')
		:attr('data-value-min', rTimeMin)
		:attr('data-value-max', rTimeMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon gun reload time.png]] Rechrg. Time')
		:done()
    	:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(rTimeMin, rTimeMax, '%1.2f s', '( %1.2f - %1.2f ) s'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-max')
		:attr('data-value-min', manaMin)
		:attr('data-value-max', manaMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon mana max.png]] Mana max')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(manaMin, manaMax, '%d', '( %d - %d )'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-charge')
		:attr('data-value-min', regenMin)
		:attr('data-value-max', regenMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon mana charge speed.png]] Mana chg. Spd')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(regenMin, regenMax, '%d', '( %d - %d )'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-cap')
		:attr('data-value-min', capMin)
		:attr('data-value-max', capMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon gun capacity.png]] Capacity')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(capMin, capMax, '%d', '( %d - %d )'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-spread')
		:attr('data-value-min', spreadMin)
		:attr('data-value-max', spreadMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon spread degrees.png]] Spread')
		:done()
		:tag('div'):attr('class', 'wand2-value')
    		:wikitext(formatRange(spreadMin, spreadMax, '%1.1f DEG', '( %1.1f - %1.1f ) DEG'))

	details:tag('div'):attr('class', 'wand2-stat')
		:attr('data-name', 'wand2-stat-speed')
		:attr('data-value-min', speedMin)
		:attr('data-value-max', speedMax)
		:tag('div'):attr('class', 'wand2-label')
    		:wikitext('[[File:Inventory Icon speed multiplier.png]] Speed')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(speedMin, speedMax, '× %1.2f', '× ( %1.2f - %1.2f )'))


  -- Simulator link
    local simURI = mw.uri.new({
      protocol = "https",
      host = "tinker-with-wands-online.vercel.app",
      query = {
        a = bothIfDifferent(pCastMin, pCastMax),
        d = bothIfDifferent(formatSeconds(delayMin),formatSeconds(delayMax)),
        r = bothIfDifferent(formatSeconds(rTimeMin),formatSeconds(rTimeMax)),
        m = bothIfDifferent(manaMin, manaMax),
        c = bothIfDifferent(regenMin, regenMax),
        l = bothIfDifferent(capMin, capMax),
        q = bothIfDifferent(spreadMin, spreadMax),
        v = bothIfDifferent(speedMin, speedMax),
        x = shuffle and 1 or 0,
        n = wandName,
        p = wandPic,
        w = table.concat(acIdList, ","),
        s = table.concat(spellIdList, ","),
      },
    })
  
  root:tag('div')
  :attr('class', 'wand2-simlink ' .. (hideLink and 'hidden' or ''))
  :tag('div'):attr('class', 'wand2-simlink-link')
    :wikitext(string.format('[%s Tinker]', tostring(simURI)))
    :done()
  :tag('div'):attr('class', 'wand2-simlink-desc')
    :tag('div'):wikitext('Visit the Wand Simulator site')
    :tag('div'):wikitext('to view & edit this wand')

  -- Always casts
  if not hideSpells and not table_empty(acIdList) then
    local alwaysCount = 0
    local alwaysContainer = nil
    for _, acId in ipairs(acIdList) do
      if alwaysCount < MAX_ALWAYSCAST and not is_empty(acId) then
        if alwaysCount < 1 then
			local always = root:tag('div'):attr('class', 'wand2-stat wand2-always')
			always:tag('div'):attr('class', 'wand2-label')
				:wikitext('[[File:Inventory Icon gun permanent actions.png]]Always casts')
          alwaysContainer = always:tag('div'):attr('class', 'wand2-value')
        end
        addSpell(alwaysContainer, results[acId] or nil, tooltips)
      end
      alwaysCount = alwaysCount + 1
    end
  end

  -- Main spells
  if not hideSpells then
      local spellsContainer = root:tag('div'):attr('class', 'wand2-spells')
      local count = 0
    for _, spellId in ipairs(spellIdList) do
      if count < cap then
        addSpell(spellsContainer, results[spellId] or nil, tooltips)
      end
      count = count + 1
    end
    while count < cap do
      addSpell(spellsContainer, nil, tooltips)
      count = count + 1
    end
  end

  root:allDone()

  return tostring(root)
end

return p