FANDOM


local card = {}
----------------------------
-- Libraries of functions --
----------------------------
-- stands for High Frequency
local HF = require('Module:HF')
-- Parses invocation and template parameters, trims whitespace, and removes blanks.
local getArgs = require('Dev:Arguments').getArgs
-- Generates lists
local L = require('Dev:List')
-- Categories / SMW properties for site maintenance
-- local M = require('Module:Card/Maintenance')
-- Indicates deprecated parameters
-- local D = require('Module:Deprecated')
-- Card Set functions
-- local CS = require('Module:Card/Set')
-- Navigation functions
local N = require('Module:Card/Navigation')
-- The video game table
local vgT = require('Module:Card/vgTable')
 
-- Gives the name of the current page. It's expensive, so we only do it once.
-- The _G then makes it global, available to all page functions.
_G.page_title = mw.title.getCurrentTitle().text
_G.namespace = mw.title.getCurrentTitle().nsText
 
-- Parses template parameters from article, trims whitespace, and removes blanks.
_G.t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
 
-- The occasions where we need the same "variables" to process multiple fields are global.
--- {{ #vardefine: $level-rank | {{ #if: {{ #pos: {{{type2}}}{{{type3}}}{{{type4}}} | Xyz }} | Rank | Level }} }}
_G.level_rank = ( string.match(
(t['type2'] or '')..
(t['type3'] or '')..
(t['type4'] or ''), 'Xyz') and 'Rank' ) or 'Level'
 
_G.kr_release = (t['kr_sets'] or t['ko_sets']) and true
_G.ae_release = t['ae_sets'] and true
_G.tc_release = t['tc_sets'] and true
_G.na_release = t['na_sets'] and true
_G.eu_release = t['eu_sets'] and true
_G.de_release = t['de_sets'] and true
_G.it_release = t['it_sets'] and true
_G.pt_release = t['pt_sets'] and true
_G.sp_release = t['sp_sets'] and true
_G.ocg_jp = (t['jp_sets'] or t['ja_sets']) and true
_G.ocg = (ocg_jp or kr_release or ae_release or tc_release) and true
_G.tcg_en = (t['en_sets'] or na_release or eu_release or t['au_sets']) and true
_G.tcg_fr = (t['fr_sets'] or t['fc_sets']) and true
_G.tcg = (tcg_en or tcg_fr or de_release or it_release or pt_release or sp_release) and true
_G.ocg_tcg = (ocg or tcg) and true
 
----------------------------------------------------------
-- Public functions (called from a Template or article) --
----------------------------------------------------------
-- English name handler
function card.name(frame)
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local pagename = HF.NP()
  local name = t['name'] or t['cardgame'] or pagename
  local sortkey = _defaultsort(pagename) or ''
  mw.smw.set{
    ['English name (linked)'] = HF.Link(pagename, name),
    ['Page name'] = pagename
  }
  return name..sortkey
end
 
-- Primary image handler
-- use {{#invoke:Card|image_front}} inside {{Card}}
function card.image_front(frame)
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local image = t['image']
  mw.smw.set{ ['Card image'] = image }
  return ( image ) .. ( t['image2'] and '{{!}}Front' or '' )
end
 
-- Primary image handler
-- use {{#invoke:Card|image_back}} inside {{Card}}
function card.image_back(frame)
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local type = t['type']
  local backs = {
    ['Strategy Card'] = 'StrategyCard-EN-Back.png',
    ['Tip Card'] = 'StrategyCard-EN-Back.png',
    ['FAQ Card'] = 'StrategyCard-EN-Back.png'
  }
  local image_back = backs[type] or ('Back-'..((tcg and 'EN') or ( ocg_jp or tc_release and 'JP' ) or ( kr_release and 'KR' ) or ( ae_release and 'AE' ) or ( vg and 'TF-EN-VG' ) or 'EN' )..'.png')
  return ( t['card_back'] or image_back )
end
 
-- Machine translation link
-- use {{#invoke:Card|translate|lang|parameter|label}} inside {{Card}}
-- "label" is optional, and defaults to 📃
function card.translate(frame)
  -- Parses invocation parameters, trims whitespace.
  local vars = getArgs(frame, { trim = true, frameOnly = true })
  assert(vars[1], 'No language code given for translation link.')
  assert(vars[2], 'No text given for translation link.')
  local language, parameter, label = vars[1], vars[2], vars[3] or '📃'
  local text = t[parameter] or ''
  local URI = mw.uri.new( { protocol = 'https', host = 'translate.google.com', path = '/#'..language..'/en/'..mw.uri.encode(text) } )
  local element = mw.html.create('span'):addClass('plainlinks'):addClass('sysop-show')
  :wikitext(HF.ExternalLink( tostring(URI), label )):allDone()
  return tostring(element)
end
 
-- Render ultimate type
-- use {{#invoke:Card|type}} inside {{Card}}
-- TODO: Migrate function to local
function card.type(frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local out = {}
  local type1, type2, type3, type4 = t['type'] or '', t['type2'], t['type3'], t['type4']
  local type1U = mw.getContentLanguage():ucfirst(type1)
  local normal_types = { 'Normal', 'Pendulum' }
  local abnormal_types = { 'Effect', 'Fusion', 'Ritual', 'Synchro', 'Token', 'Xyz', 'Link' }
  local non_effect_monster_types = { 'Fusion', 'Ritual', 'Synchro', 'Xyz', 'Link' }
  local type3_types = { 'Tuner', 'Spirit', 'Flip' }
  local monster = (t['atk'] or t['def'] or t['level'] or t['rank'] or t['link_arrows'] or t['type2'] == 'Token') and true
  local effect_monster = (
  t['effect_types'] or
  (t['type2'] and t['type2']:match('Effect')) or
  (t['type3'] and t['type3']:match('Effect')) or
  (t['type4'] and t['type4']:match('Effect'))
  ) and true
  local non_effect_monster = (
  (type2 and non_effect_monster_types[type2]) or
  (type3 and non_effect_monster_types[type3]) or
  (type4 and non_effect_monster_types[type4])
  ) and true
  local pendulum_monster = (
  t['pendulum_effect']
  or t['pendulum_effect_types']
  or  (t['type2'] and t['type2']:match('Pendulum')) or
  (t['type3'] and t['type3']:match('Pendulum')) or
  (t['type4'] and t['type4']:match('Pendulum'))) and true
  local switch = {
    [''] = "",
    ['Equip Card'] = HF.Link(type1U),
    ['Tip Card'] = HF.Link(type1U),
    ['Strategy Card'] = HF.Link(type1U),
    ['FAQ Card'] = HF.Link(type1U),
    ['Illustration Card'] = HF.Link(type1U),
    ['Counter'] = HF.Link(type1U),
  }
  local special_types = {
    ['Flip'] = "Flip monster",
    ['Spirit'] = "Spirit monster",
    ['Union'] = "Union monster",
    ['Toon'] = "Toon monster",
    ['Gemini'] = "Gemini monster",
    ['Tuner'] = "Tuner monster",
    ['Dark Tuner'] = "Dark Tuner monster",
  }
  if t['type'] or type2 or type3 or type4 then
    if switch[type1] and type1 ~= '' then
      mw.smw.set{ ['Card type'] = type1 }
    else
      mw.smw.set{ ['Type'] = type1 }
    end
    local type1_prefix = type1:match('(%a*)%(') or type1
    table.insert(out,
    switch[type1] or
    HF.Link(type1U, type1_prefix) )
    if type2 then
      table.insert(out, HF.Link( special_types[type2] or type2..' Monster' , type2))
      if abnormal_types[type2] or normal_types[type2] then
        mw.smw.set{
          ['Primary type'] = type2..' Monster',
          ['Card type'] = type2..' Monster',
        }
      elseif special_types[type2] and (not abnormal_types[type2]) then
        mw.smw.set{
          ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
          ['Card type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
          ['Secondary type'] = type2..' Monster',
          ['Monster type'] = type2..' monster'
        }
      elseif special_types[type2] then
        mw.smw.set{
          ['Secondary type'] = type2..' Monster',
          ['Monster type'] = type2..' monster'
        }
      end
    end
    if type3 then
      table.insert(out, HF.Link( special_types[type3] or type3..' Monster', type3))
      mw.smw.set{['Type3'] = type3}
      if type3_types[type3] and
      (not abnormal_types[type2]) then
        mw.smw.set{
          ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
          ['Secondary type'] = type3..' monster',
          ['Monster type'] = type3..' monster',
          ['Card type'] = effect_monster and 'Effect' or 'Normal',
        }
      elseif type3_types[type3] then
        mw.smw.set{
          ['Secondary type'] = type3..' monster',
          ['Card type'] = effect_monster and 'Effect',
          ['Monster type'] = type3..' monster',
        }
      end
    end
    if type4 then
      table.insert(out, HF.Link( type4..' Monster', type4))
      mw.smw.set{['Type4'] = type4}
    end
  end
  if monster or t['type'] and
  not(effect_monster or t['type2'] or t['attribute']:uc():match('DIVINE') or token_counter or non_game) then
    mw.smw.set{
      ['Primary type'] = 'Normal Monster',
      ['Card type'] = 'Normal Monster'
    }
  end
  if pendulum_monster then
    mw.smw.set{
      ['Primary type'] = 'Pendulum Monster'
    }
    if not(non_effect_monster) then
      mw.smw.set{
        ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
        ['Card type'] = effect_monster and 'Effect Monster' or 'Normal Monster'
      }
    end
  end
 
  if not(ocg_tcg) then
    if t['type2'] == 'Token' then mw.smw.set{['Class 1'] = 'NR Tokens'} end
  end
 
  return table.concat(out, ' / ')
end
 
-- use {{#invoke:Card|card_type}} inside {{Card}}
-- TODO: Migrate function to local
function card.card_type (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local monster = (t['atk'] or t['def'] or t['level'] or t['rank'] or t['link_arrows'] or t['type2'] == 'Token') and true
  local out = {}
  if monster or t['card_type'] then
    local m = (t['card_type'] or 'Monster')..' Card'
    table.insert(out, HF.Link( m, (t['card_type'] or 'Monster')))
    if t['card_type'] == 'Speed Spell' then
      table.insert(out, '[[File:SPELL.svg|28px|alt=]] [[File:Speed Spell.svg|x28px|alt=]]')
    elseif not monster then
      table.insert(out, '[[File:'..string.upper(t['card_type'])..'.svg|28px|alt=]]')
    end
    mw.smw.set{
      ['Card type'] = m,
      ['Card type (short)'] = HF.Link( m, (t['card_type'] or 'Monster') )
    }
    if monster then
      mw.smw.set{['Card category'] = 'Monster Card'}
    end
  end
  return table.concat(out)
end
 
-- use {{#invoke:Card|level}} inside {{Card}}
-- TODO: Migrate function to local
function card.level (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local stars = t['level'] or t['rank'] or nil
  local out, SMW = {}
  if stars then
    table.insert(out, HF.Link(level_rank..' '..stars..' Monster Cards', stars))
    if ocg_tcg or vg then
      table.insert(out,
      string.rep( '[[File:'..((level_rank == 'Rank') and 'Rank' or 'CG')..' '..( tonumber(stars) and 'Star' or 'Star Unknown')..'.svg|18px|link=]]', ( tonumber(stars) or 1 ) )
      )
    end
    mw.smw.set{
      [( tonumber(stars) and '' or '?' )..' '..level_rank] = stars,
      [level_rank..' string'] = stars,
      [( tonumber(stars) and '' or '?' )..'Stars'] = stars,
      ['Stars string'] = stars
    }
    return table.concat(out)
  end
end
 
-- Formats card sets in rows
-- Detects if the content uses a wrapper template, or is data
-- use {{#invoke:Card|cardset_format|{{{parameter}}}|}} inside {{Card}}
function card.cardset_format (frame)
  local vars = getArgs(frame, { trim = true, frameOnly = true })
  local out = {}
  -- no logic yet
  table.insert(out, _cardset_envelope(vars))
  return table.concat(out)
end
 
-- Detects if the content uses a wrapper template, or is data
-- use {{#invoke:Card|cardset_categories|{{{parameter}}}|property}} inside {{Card}}
-- TODO: Migrate function to local
function card.search_categories (frame)
  -- Parses invocation parameters, trims whitespace.
  local vars = getArgs(frame, { trim = true, frameOnly = true })
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  local parameter = assert(vars[1], 'No parameter is specified.')
  local value = t[parameter]
  local property = vars[2]
  local out = {} -- output
 
  local results = HF.unique(mw.text.split(value, '%*'))
  local list_items = {}
 
  if #results > 0 then
    for i, v in ipairs( results ) do
      v_unlinked = ( v:match( '%[%[:?(.-)[|%]]' ) or HF.trim( v ) )
      table.insert( list_items,
      HF.Link( v_unlinked, ( v_unlinked:match('(.+)%(') or v_unlinked ) )
      )
      mw.smw.set{ property..'='..v_unlinked }
    end
    local list = L.makeList( 'horizontal' , list_items )
    table.insert(out, tostring(list))
  end
  if parameter == 'summoning' and t['monster'] and (not t['token']) then
    if (not value:lower():match('nomi')) and
    not (value:lower():match('cannot special summon') or value:lower():match('cannot be special summoned')) then
      mw.smw.set{['Summoning'] = 'Can be Special Summoned' }
      if not ( value:lower():match('semi[ -]?nomi') or
      value:lower():match('special summon-only monster') or
      t['monster_type']) then
        mw.smw.set{['Summoning'] = 'Can always be Special Summoned' }
      end
    end
  end
  return table.concat(out)
end
 
function card.ctaama (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local args = getArgs(frame, { trim = true, frameOnly = true })
  --{{ctaama|{{{appears_in_5d|}}} | series = 5D's | type = Episode }}
  return _ctaama( args[1], args['series'], args['type'] )
end
 
function card.vg_table (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return vgT._table(t)
end
 
function card.related (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return N.related(t)
end
 
function card.related_tabview (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return N.relatedTabView(t)
end
 
function card.external (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return N.external(t)
end
 
function card.yugioh_card_db (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return N.yugioh_card_db(t)
end
 
function card.specialized_pages (frame)
  -- Parses template parameters, trims whitespace, and removes blanks.
  local t = getArgs(frame, { trim = true, removeBlanks = true, parentOnly = true })
  return N.specialized_pages(t)
end
 
---------------------------------------------------------
-- Internal functions (used in this and other Modules) --
---------------------------------------------------------
-- Create an envelope for existing tables
function _cardset_envelope (inv_args)
  local envelope = mw.html.create('div')
  :addClass('mw-collapsible'):addClass('mw-collapsed')
  :attr('data-expandtext','Show'):attr('data-collapsetext','Hide')
  :wikitext(inv_args[1]):allDone()
  return tostring(envelope)
end
 
function _defaultsort (PN)
  local SORTKEY = ''
  if PN:match('^A ') then
    return '{{'..'DEFAULTSORT:'..PN:match('^A (.*)')..'}}'
  elseif PN:match('^An ') then
    return '{{'..'DEFAULTSORT:'..PN:match('^An (.*)')..'}}'
  elseif PN:match('^The ') and not (PN:match('The Agent') or PN:match('The Fabled')) then
    return '{{'..'DEFAULTSORT:'..PN:match('^The (.*)')..'}}'
  elseif (PN:match('The Agent') or PN:match('The Fabled')) then
    return
  elseif PN:match('^Number %a+%d+') then
    local epoch, order, remainder = PN:match('^Number (%a+)(%d+)(.*)')
    if tonumber(order) < 10 then order = '000'..order
  elseif tonumber(order) < 100 then order = '00'..order
  elseif tonumber(order) < 1000 then order = '0'..order
  end
  return '{{'..'DEFAULTSORT:Number '..epoch..order..remainder..'}}'
else
  return
end
end
 
function _ctaama( parameter, series, appearance_type )
local _s = 'Yu-Gi-Oh! '..(series or '')
local _t = ( assert(appearance_type, 'No appearance type declared') == 'Episode' ) and 'episode' or 'chapter'
local _e = HF.unique(mw.text.split(parameter, ','))
local app_prop_link = ('%s %s appearances (linked)'):format(_s, _t)
local app_prop = ('%s %s appearances'):format(_s, _t)
local o = {}
for _,_episode in ipairs(_e) do
  --_episode = mw.text.trim(_episode)
  _target = ( #_episode < 4 ) and ('Yu-Gi-Oh! %s - %s  %s'):format(series or '', appearance_type, _episode) or _episode
  _link = HF.Link( _target, _episode )
  mw.smw.set{
    app_prop_link = _link,
    app_prop = _episode
  }
  table.insert( o, _link )
end
return table.concat( o, ",&#32;")
end
 
function _CT2effect( )
-- See {{CardTable2/effect}}
end
 
------------------------------------------------
-- Local functions (only used in this Module) --
------------------------------------------------
 
-- Primary image handler
function _image_front(args)
local image = args['image']
mw.smw.set{ ['Card image'] = image }
return ( image ) .. ( args['image2'] and '{{!}}Front' or '' )
end
 
-- Secondary image handler
function _image_back(args)
local type = args['type']
local backs = {
  ['Strategy Card'] = 'StrategyCard-EN-Back.png',
  ['Tip Card'] = 'StrategyCard-EN-Back.png',
  ['FAQ Card'] = 'StrategyCard-EN-Back.png'
}
local image_back = backs[type] or ('Back-'..((args['tcg'] and 'EN') or ( args['ocg_jp'] or args['tc_release'] and 'JP' ) or ( args['kr_release'] and 'KR' ) or ( args['ae_release'] and 'AE' ) or ( args['vg'] and 'TF-EN-VG' ) or 'EN' )..'.png')
return ( args['card_back'] or image_back )
end
 
-- Render ultimate type
function _card_type(args)
local out = {}
local type1, type2, type3, type4 = args['type'] or '', args['type2'], args['type3'], args['type4']
local type1U = mw.getContentLanguage():ucfirst(type1)
local normal_types = { 'Normal', 'Pendulum' }
local abnormal_types = { 'Effect', 'Fusion', 'Ritual', 'Synchro', 'Token', 'Xyz', 'Link' }
local non_effect_monster_types = { 'Fusion', 'Ritual', 'Synchro', 'Xyz', 'Link' }
local type3_types = { 'Tuner', 'Spirit', 'Flip' }
local monster = (args['atk'] or args['def'] or args['level'] or args['rank'] or args['link_arrows'] or args['type2'] == 'Token') and true
local effect_monster = (
args['effect_types'] or
(args['type2'] and args['type2']:match('Effect')) or
(args['type3'] and args['type3']:match('Effect')) or
(args['type4'] and args['type4']:match('Effect'))
) and true
local non_effect_monster = (
(type2 and non_effect_monster_types[type2]) or
(type3 and non_effect_monster_types[type3]) or
(type4 and non_effect_monster_types[type4])
) and true
local pendulum_monster = (
args['pendulum_effect']
or args['pendulum_effect_types']
or  (args['type2'] and args['type2']:match('Pendulum')) or
(args['type3'] and args['type3']:match('Pendulum')) or
(args['type4'] and args['type4']:match('Pendulum'))) and true
local switch = {
  [''] = "",
  ['Equip Card'] = HF.Link(type1U),
  ['Tip Card'] = HF.Link(type1U),
  ['Strategy Card'] = HF.Link(type1U),
  ['FAQ Card'] = HF.Link(type1U),
  ['Illustration Card'] = HF.Link(type1U),
  ['Counter'] = HF.Link(type1U),
}
local special_types = {
  ['Flip'] = "Flip monster",
  ['Spirit'] = "Spirit monster",
  ['Union'] = "Union monster",
  ['Toon'] = "Toon monster",
  ['Gemini'] = "Gemini monster",
  ['Tuner'] = "Tuner monster",
  ['Dark Tuner'] = "Dark Tuner monster",
}
if args['type'] or type2 or type3 or type4 then
  if switch[type1] and type1 ~= '' then
    mw.smw.set{ ['Card type'] = type1 }
  else
    mw.smw.set{ ['Type'] = type1 }
  end
  local type1_prefix = type1:match('(%a*)%(') or type1
  table.insert(out,
  switch[type1] or
  HF.Link(type1U, type1_prefix) )
  if type2 then
    table.insert(out, HF.Link( special_types[type2] or type2..' Monster' , type2))
    if abnormal_types[type2] or normal_types[type2] then
      mw.smw.set{
        ['Primary type'] = type2..' Monster',
        ['Card type'] = type2..' Monster',
      }
    elseif special_types[type2] and (not abnormal_types[type2]) then
      mw.smw.set{
        ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
        ['Card type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
        ['Secondary type'] = type2..' Monster',
        ['Monster type'] = type2..' monster'
      }
    elseif special_types[type2] then
      mw.smw.set{
        ['Secondary type'] = type2..' Monster',
        ['Monster type'] = type2..' monster'
      }
    end
  end
  if type3 then
    table.insert(out, HF.Link( special_types[type3] or type3..' Monster', type3))
    mw.smw.set{['Type3'] = type3}
    if type3_types[type3] and
    (not abnormal_types[type2]) then
      mw.smw.set{
        ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
        ['Secondary type'] = type3..' monster',
        ['Monster type'] = type3..' monster',
        ['Card type'] = effect_monster and 'Effect' or 'Normal',
      }
    elseif type3_types[type3] then
      mw.smw.set{
        ['Secondary type'] = type3..' monster',
        ['Card type'] = effect_monster and 'Effect',
        ['Monster type'] = type3..' monster',
      }
    end
  end
  if type4 then
    table.insert(out, HF.Link( type4..' Monster', type4))
    mw.smw.set{['Type4'] = type4}
  end
end
if monster or args['type'] and
not(effect_monster or args['type2'] or args['attribute']:uc():match('DIVINE') or token_counter or non_game) then
  mw.smw.set{
    ['Primary type'] = 'Normal Monster',
    ['Card type'] = 'Normal Monster'
  }
end
if pendulum_monster then
  mw.smw.set{
    ['Primary type'] = 'Pendulum Monster'
  }
  if not(non_effect_monster) then
    mw.smw.set{
      ['Primary type'] = effect_monster and 'Effect Monster' or 'Normal Monster',
      ['Card type'] = effect_monster and 'Effect Monster' or 'Normal Monster'
    }
  end
end
 
if not(args['ocg_tcg']) then
  if args['type2'] == 'Token' then mw.smw.set{['Class 1'] = 'NR Tokens'} end
end
 
return table.concat(out, ' / ')
end
 
-------------------------------------------------
-- Output (send it back to whatever called it) --
-------------------------------------------------
return card
*Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you, Fandom will earn a commission if you click through and make a purchase. Community content is available under CC-BY-SA unless otherwise noted.

Fandom may earn an affiliate commission on sales made from links on this page.

Stream the best stories.

Fandom may earn an affiliate commission on sales made from links on this page.

Get Disney+