モジュール:Wand

提供:Noita Wiki
ナビゲーションに移動 検索に移動

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, duplicating and extending their functionality. The template includes a link to view and edit the wand on the Wand Simulator site, which can also export back to this template format for inclusion into wiki articles.

Spells are now specified using their ID, rather than name, which simplifies exporting/importing Wand builds to mods and other tools.

Some useful resources:

  • List of legacy Wand templates (to be converted): Category:Pages with wand templates
  • {{SpellTypeClass}} {{SpellTypeColour}} - spell type classes and CSS variables for styling
  • {{SpellCategory}} - spell groupings based on tags
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 シャッフル
いいえ
Inventory Icon gun actions per round.png 同時詠唱数
1
Inventory Icon fire rate wait.png] 詠唱遅延
0.17 秒
Inventory Icon gun reload time.png リチャージ時間
0.48 秒
Inventory Icon mana max.png 最大マナ
900
Inventory Icon mana charge speed.png マナチャージ速度
700
Inventory Icon gun capacity.png 呪文容量
16
Inventory Icon spread degrees.png 拡散
-2.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
Inventory Icon gun permanent actions.png常に呪文を詠唱
ホーミング誘導
ホーミング誘導
放射物が敵の方に向かって加速するようになる
タイプ
放射物調整盤
マナ流出
ホーミング誘導
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
10分割
10分割
次の呪文を10回詠唱しますが、ダメージは減少します
タイプ
その他
マナ流出
10分割
ダメージプラス
ダメージプラス
放射物によるダメージを増加させる
タイプ
放射物調整盤
マナ流出
ダメージプラス
トリプリケイトボルト
トリプリケイトボルト
小さく素早いボルトの編隊
タイプ
放射物
マナ流出
トリプリケイトボルト
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 シャッフル
いいえ
Inventory Icon gun actions per round.png 同時詠唱数
1
Inventory Icon fire rate wait.png] 詠唱遅延
0.17 秒
Inventory Icon gun reload time.png リチャージ時間
0.48 秒
Inventory Icon mana max.png 最大マナ
900
Inventory Icon mana charge speed.png マナチャージ速度
700
Inventory Icon gun capacity.png 呪文容量
16
Inventory Icon spread degrees.png 拡散
-2.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
Inventory Icon gun permanent actions.png常に呪文を詠唱
ホーミング誘導
ホーミング誘導
放射物が敵の方に向かって加速するようになる
タイプ
放射物調整盤
マナ流出
ホーミング誘導
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
10分割
10分割
次の呪文を10回詠唱しますが、ダメージは減少します
タイプ
その他
マナ流出
10分割
ダメージプラス
ダメージプラス
放射物によるダメージを増加させる
タイプ
放射物調整盤
マナ流出
ダメージプラス
トリプリケイトボルト
トリプリケイトボルト
小さく素早いボルトの編隊
タイプ
放射物
マナ流出
トリプリケイトボルト
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 シャッフル
いいえ
Inventory Icon gun actions per round.png 同時詠唱数
2
Inventory Icon fire rate wait.png] 詠唱遅延
0.50 秒
Inventory Icon gun reload time.png リチャージ時間
1.00 秒
Inventory Icon mana max.png 最大マナ
500
Inventory Icon mana charge speed.png マナチャージ速度
500
Inventory Icon gun capacity.png 呪文容量
24
Inventory Icon spread degrees.png 拡散
0.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
Inventory Icon gun permanent actions.png常に呪文を詠唱
蔓延
ホーミング誘導
集中ライト
ダメージプラス
マナを追加する
マナを追加する
マナを追加する
マナを追加する
マナを追加する
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
10分割
ダメージプラス
トリプリケイトボルト
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 シャッフル
いいえ
Inventory Icon gun actions per round.png 同時詠唱数
2
Inventory Icon fire rate wait.png] 詠唱遅延
0.50 秒
Inventory Icon gun reload time.png リチャージ時間
1.00 秒
Inventory Icon mana max.png 最大マナ
500
Inventory Icon mana charge speed.png マナチャージ速度
500
Inventory Icon gun capacity.png 呪文容量
24
Inventory Icon spread degrees.png 拡散
0.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
Inventory Icon gun permanent actions.png常に呪文を詠唱
蔓延
ホーミング誘導
集中ライト
ダメージプラス
マナを追加する
マナを追加する
マナを追加する
マナを追加する
マナを追加する
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
10分割
ダメージプラス
トリプリケイトボルト
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 シャッフル
いいえ
Inventory Icon gun actions per round.png 同時詠唱数
( 2 - 7 )
Inventory Icon fire rate wait.png] 詠唱遅延
( 0.50 - 1.20 ) 秒
Inventory Icon gun reload time.png リチャージ時間
( 1.00 - 2.00 ) 秒
Inventory Icon mana max.png 最大マナ
( 500 - 7000 )
Inventory Icon mana charge speed.png マナチャージ速度
( 5 - 2000 )
Inventory Icon gun capacity.png 呪文容量
( 2 - 16 )
Inventory Icon spread degrees.png 拡散
( -20.0 - 80.0 )  度
Inventory Icon speed multiplier.png 速度
× ( 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 シャッフル
はい
Inventory Icon gun actions per round.png 同時詠唱数
1
Inventory Icon fire rate wait.png] 詠唱遅延
0.17 秒
Inventory Icon gun reload time.png リチャージ時間
0.48 秒
Inventory Icon mana max.png 最大マナ
900
Inventory Icon mana charge speed.png マナチャージ速度
700
Inventory Icon gun capacity.png 呪文容量
12
Inventory Icon spread degrees.png 拡散
-2.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
Inventory Icon gun permanent actions.png常に呪文を詠唱
蔓延
蔓延
蔓延
蔓延
マナを追加する
マナを追加する
マナを追加する
マナを追加する
マナを追加する
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
10分割
ダメージプラス
トリプリケイトボルト
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 シャッフル
はい
Inventory Icon gun actions per round.png 同時詠唱数
1
Inventory Icon fire rate wait.png] 詠唱遅延
0.17 秒
Inventory Icon gun reload time.png リチャージ時間
0.48 秒
Inventory Icon mana max.png 最大マナ
900
Inventory Icon mana charge speed.png マナチャージ速度
700
Inventory Icon gun capacity.png 呪文容量
12
Inventory Icon spread degrees.png 拡散
-2.0  度
Inventory Icon speed multiplier.png 速度
× 1.00
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
マナを追加する
マナを追加する
30マナを杖に追加する
タイプ
放射物調整盤
マナ流出
マナを追加する
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
リチャージ時間減少
リチャージ時間減少
呪文詠唱間隔(時間)を減少させる
タイプ
放射物調整盤
マナ流出
リチャージ時間減少
10分割
10分割
次の呪文を10回詠唱しますが、ダメージは減少します
タイプ
その他
マナ流出
10分割
ダメージプラス
ダメージプラス
放射物によるダメージを増加させる
タイプ
放射物調整盤
マナ流出
ダメージプラス
トリプリケイトボルト
トリプリケイトボルト
小さく素早いボルトの編隊
タイプ
放射物
マナ流出
トリプリケイトボルト

Parameters

  • All parameters are optional.
  • Parameters without a default value are omitted from the output entirely.
  • Both templates {{Wand Card}} and {{Wand}} share the same parameter names and they may be used interchangeably.
  • The numbering of the spell[X] entries is to make them unique, and does not affect the order of spells in the wand. Spells will appear in the order they are listed, and blank spells will only appear where there is an empty entry.
  • Ranges for a value may be displayed by enclosing them in brackets, e.g.: (0.17 - 0.34). Values with ranges will be omitted from the Wand Simulator link url.
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]
wandPic Wand_0821.png Specifies the wand image. Must include the filetype extension, e.g.: Wand handgun.png [notes 1]
shuffle No Whether or not the wand is a shuffle-type wand. Can be Yes or No.
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 The spell that the wand always casts. [notes 1]
spell1, spell2 ... spell(n) Comprises a list of the case sensitive names of the spells on the wand. The numbering must be unique, but has no effect on spell order - spells will appear on the wand in the order they're listed. However, spells numbered greater than specified wand capacity will not appear. An empty entry in the list produces an empty spell slot.
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 screens with width of less than 640px.) Set to No to disable the vertical mode entirely.
  1. 1.0 1.1 1.2 1.3 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 = {
  ["放射物"] = "spellProjectile",
  ["静電放射物"] = "spellStatic",
  ["放射物調整盤"] = "spellModifier",
  ["マルチキャスト"] = "spellMulticast",
  ["資材"] = "spellMaterial",
  ["その他"] = "spellOther",
  ["ユーティリティー"] = "spellUtility",
  ["受け身"] = "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('タイプ'))
      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('マナ流出'))
      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]] シャッフル')
    	:done()
    	:tag('div'):attr('class', 'wand2-value')
        	:wikitext(string.format('%s', shuffle and 'はい' or 'いいえ'))

	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]] 同時詠唱数')
    	: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]]] 詠唱遅延')
		:done()
		:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(delayMin, delayMax, '%1.2f 秒', '( %1.2f - %1.2f ) 秒'))

	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]] リチャージ時間')
		:done()
    	:tag('div'):attr('class', 'wand2-value')
        	:wikitext(formatRange(rTimeMin, rTimeMax, '%1.2f 秒', '( %1.2f - %1.2f ) 秒'))

	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]] 最大マナ')
		: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]] マナチャージ速度')
		: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]] 呪文容量')
		: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]] 拡散')
		:done()
		:tag('div'):attr('class', 'wand2-value')
    		:wikitext(formatRange(spreadMin, spreadMax, '%1.1f  度', '( %1.1f - %1.1f )  度'))

	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]] 速度')
		: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]]常に呪文を詠唱')
          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