|
1 |
| -{% extends '/layout.html.twig' %} |
2 |
| - |
3 |
| -{% block title %}Ban Lists{% endblock %} |
4 |
| - |
5 |
| -{% block body %} |
6 |
| -{% include '/Scripts/api.html.twig' %} |
7 |
| -{% include '/Scripts/panels.html.twig' %} |
8 |
| -<div class="container" id="banlists"> |
9 |
| - <h1>{{ block('title') }}</h1> |
10 |
| - <div> |
11 |
| - <p>There are currently three official <a href="{{ path('formats') }}">formats</a> supported by NISEI: Startup, Standard, and Eternal. This page displays the ban lists for each.</p> |
12 |
| - <h2>Explanation</h2> |
13 |
| - <ul> |
14 |
| - <li><b>Banned:</b> You cannot include any copies of a banned card in your deck.</li> |
15 |
| - <li><b>Points:</b> Including any number of copies of a card with points in Eternal adds points to your deck. Eternal decks must have 7 points or fewer.</li> |
16 |
| - <li><b>Restricted (deprecated):</b> You may include up to a full playset of only one restricted card.</li> |
17 |
| - <li><b>Universal Influence (deprecated):</b> Cards with universal influence cost additional influence to include in a deck.</li> |
18 |
| - <li><b>Identity Influence Reduction (deprecated):</b> These cards reduce your identity's influence limit by 1 for each copy (to a minimum of 1).</li> |
19 |
| - </ul> |
20 |
| - <p>See <a href="https://nisei.net/players/supported-formats/">NISEI's Supported Formats page</a> for more information.</p> |
21 |
| - </div> |
22 |
| - <hr> |
23 |
| - <div id="restrictions-root" role="tabpanel" style="display: none;"> |
24 |
| - <!-- Nav tabs --> |
25 |
| - <ul class="nav nav-pills nav-justified" role="tablist" style="margin-bottom:20px"> |
26 |
| - <li role="presentation"><a href="#tab-pane-startup" role="tab" data-toggle="tab">Startup</a></li> |
27 |
| - <li role="presentation" class="active"><a href="#tab-pane-standard" role="tab" data-toggle="tab">Standard</a></li> |
28 |
| - <li role="presentation"><a href="#tab-pane-eternal" role="tab" data-toggle="tab">Eternal</a></li> |
29 |
| - </ul> |
30 |
| - <hr> |
31 |
| - <!-- Tab panes --> |
32 |
| - <div class="tab-content"> |
33 |
| - <div role="tabpanel" class="tab-pane" id="tab-pane-startup"></div> |
34 |
| - <div role="tabpanel" class="tab-pane active" id="tab-pane-standard"></div> |
35 |
| - <div role="tabpanel" class="tab-pane" id="tab-pane-eternal"></div> |
36 |
| - </div> |
37 |
| - </div> |
38 |
| - |
39 |
| - <noscript> |
40 |
| - <p class="text-center">Please enable JavaScript to view this page fully.</p> |
41 |
| - <hr> |
42 |
| - </noscript> |
43 |
| -</div> |
44 |
| - |
45 |
| -<script> |
46 |
| -// Takes a list of cards and an object mapping integers to lists of card IDs |
47 |
| -// Returns a new object with the cards in place of their IDs |
48 |
| -function makeCardMap(cards, obj) { |
49 |
| - return Object.keys(obj).reduce(function(newObj, key) { |
50 |
| - newObj[key] = cards.filter(card => obj[key].includes(card.id)); |
51 |
| - return newObj |
52 |
| - }, {}); |
53 |
| -} |
54 |
| -
|
55 |
| -// Creates a list of cards with the given header |
56 |
| -function generateList(header, cards, pre='') { |
57 |
| - if (pre.length > 0) { |
58 |
| - pre = `<li>${pre}</li>`; |
59 |
| - } |
60 |
| - return cards.reduce((text, card) => { |
61 |
| - return text + `<li><a href="${cardToLatestPrintingLink(card)}">${card.attributes.title}</a></li>`; |
62 |
| - }, `<li><strong>${header}</strong><ul>${pre}`) + '</ul></li>'; |
63 |
| -} |
64 |
| -
|
65 |
| -async function buildBanlistsView() { |
66 |
| - // Unhide the restrictions view (accounting for browsers with JS disabled) |
67 |
| - $('#restrictions-root').show(); |
68 |
| -
|
69 |
| - // Add a temporary loading indicator |
70 |
| - $('.tab-pane').append(loading_icon); |
71 |
| -
|
72 |
| - // Load data from API |
73 |
| - const desiredFormats = ['startup', 'standard', 'eternal']; |
74 |
| - const [formats, cards, restrictions] = await Promise.all([ |
75 |
| - fetchData(`{{ v3_api_url }}/api/v3/public/formats`).then(fs => fs.filter(f => desiredFormats.includes(f.id))), |
76 |
| - fetchCards(`?include=card_subtypes&filter[search]=in_restriction:true`, 250).then(splitBySide), |
77 |
| - fetchData(`{{ v3_api_url }}/api/v3/public/restrictions?sort=-date_start`) |
78 |
| - ]); |
79 |
| -
|
80 |
| - // Remove the loading indicator |
81 |
| - $('.temp-loading').remove(); |
82 |
| -
|
83 |
| - // Add each format to the page |
84 |
| - formats.forEach(f => { |
85 |
| - const jqPane = $(`#tab-pane-${f.id}`); |
86 |
| - const formatRestrictions = restrictions.filter(r => f.attributes.restriction_ids.includes(r.id)); |
87 |
| - if (formatRestrictions.length == 0) { |
88 |
| - jqPane.append(`<p>No cards are currently banned in ${f.attributes.name}. Have a blast!</p>`); |
89 |
| - } else { |
90 |
| - const panelList = new PanelList(jqPane, null, false, "toggle", "search"); |
91 |
| - formatRestrictions.forEach(r => { |
92 |
| - const active = r.id == f.attributes.active_restriction_id; |
93 |
| - const visible = active || r.id == formatRestrictions[0].id; |
94 |
| -
|
95 |
| - // Create panel |
96 |
| - const panel = panelList.createPanel(r.id, visible); |
97 |
| - panel.addHeader(r.attributes.name); |
98 |
| - panel.addSubheader(`<a href=${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':`b!${r.id.replaceAll('_', '-')}`})}>${r.attributes.size} cards</a>. Start Date: ${r.attributes.date_start}.`); |
99 |
| -
|
100 |
| - // Get content data |
101 |
| - const v = r.attributes.verdicts; |
102 |
| - // Lists (bans, restricted cards, global penalty cards) |
103 |
| - const [corpBan, runnerBan] = cards.map(cs => cs.filter(card => v.banned?.includes(card.id))); |
104 |
| - const [corpRes, runnerRes] = cards.map(cs => cs.filter(card => v.restricted?.includes(card.id))); |
105 |
| - const [corpPen, runnerPen] = cards.map(cs => cs.filter(card => v.global_penalty?.includes(card.id))); |
106 |
| - // Mappings (points, universal faction costs) |
107 |
| - const [corpUFC, runnerUFC] = cards.map(cs => makeCardMap(cs, v.universal_faction_cost)); |
108 |
| - const [corpPts, runnerPts] = cards.map(cs => makeCardMap(cs, v.points)); |
109 |
| -
|
110 |
| - // Add body |
111 |
| - panel.addBody(); |
112 |
| - panel.addBodyContent(`<div class="container-fluid"><div class="row flex-fill"><div class="col-md-6"><h3>Corp Cards</h3><ul class="corp"></ul></div><div class="col-md-6"><h3>Runner Cards</h3><ul class="runner"></ul></div></div></div>`); |
113 |
| -
|
114 |
| - // Generate corp restrictions |
115 |
| - const jqCorp = panel.body.find('ul.corp'); |
116 |
| - // Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length) |
117 |
| - if (corpBan.length > 0) { |
118 |
| - if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents |
119 |
| - const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:corp'})}">Current</a></strong> subtype.`; |
120 |
| - jqCorp.append(generateList('Banned', removeCurrents(corpBan), pre)); |
121 |
| - } else { |
122 |
| - jqCorp.append(generateList('Banned', corpBan)); |
123 |
| - } |
124 |
| - } |
125 |
| - // The others |
126 |
| - if (corpRes.length > 0) { jqCorp.append(generateList('Restricted', corpRes)); } |
127 |
| - Object.keys(corpUFC).sort().reverse().forEach(p => { jqCorp.append(generateList(`+${p} Universal Influence`, corpUFC[p])); }); |
128 |
| - if (corpPen.length > 0) { jqCorp.append(generateList('Identity Influence Reduction', corpPen)); } |
129 |
| - Object.keys(corpPts).sort().reverse().forEach(p => { jqCorp.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, corpPts[p])); }); |
130 |
| -
|
131 |
| - // Generate runner restrictions |
132 |
| - const jqRunner = panel.body.find('ul.runner'); |
133 |
| - // Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length) |
134 |
| - if (runnerBan.length > 0) { |
135 |
| - if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents |
136 |
| - const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:runner'})}">Current</a></strong> subtype.`; |
137 |
| - jqRunner.append(generateList('Banned', removeCurrents(runnerBan), pre)); |
138 |
| - } else { |
139 |
| - jqRunner.append(generateList('Banned', runnerBan)); |
140 |
| - } |
141 |
| - } |
142 |
| - // The others |
143 |
| - if (runnerRes.length > 0) { jqRunner.append(generateList('Restricted', runnerRes)); } |
144 |
| - Object.keys(runnerUFC).sort().reverse().forEach(p => { jqRunner.append(generateList(`+${p} Universal Influence`, runnerUFC[p])); }); |
145 |
| - if (runnerPen.length > 0) { jqRunner.append(generateList('Identity Influence Reduction', runnerPen)); } |
146 |
| - Object.keys(runnerPts).sort().reverse().forEach(p => { jqRunner.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, runnerPts[p])); }); |
147 |
| - }); |
148 |
| - } |
149 |
| - }); |
150 |
| -} |
151 |
| -
|
152 |
| -// Create the banlists view on load |
153 |
| -buildBanlistsView(); |
154 |
| -</script> |
155 |
| -
|
156 |
| -{% endblock %} |
| 1 | +{% extends '/layout.html.twig' %} |
| 2 | + |
| 3 | +{% block title %}Ban Lists{% endblock %} |
| 4 | + |
| 5 | +{% block body %} |
| 6 | +{% include '/Scripts/api.html.twig' %} |
| 7 | +{% include '/Scripts/panels.html.twig' %} |
| 8 | +<div class="container" id="banlists"> |
| 9 | + <h1>{{ block('title') }}</h1> |
| 10 | + <div> |
| 11 | + <p>There are currently three official <a href="{{ path('formats') }}">formats</a> supported by Null Signal Games: Startup, Standard, and Eternal. This page displays the ban lists for each.</p> |
| 12 | + <h2>Explanation</h2> |
| 13 | + <ul> |
| 14 | + <li><b>Banned:</b> You cannot include any copies of a banned card in your deck.</li> |
| 15 | + <li><b>Points:</b> Including any number of copies of a card with points in Eternal adds points to your deck. Eternal decks must have 7 points or fewer.</li> |
| 16 | + <li><b>Restricted (deprecated):</b> You may include up to a full playset of only one restricted card.</li> |
| 17 | + <li><b>Universal Influence (deprecated):</b> Cards with universal influence cost additional influence to include in a deck.</li> |
| 18 | + <li><b>Identity Influence Reduction (deprecated):</b> These cards reduce your identity's influence limit by 1 for each copy (to a minimum of 1).</li> |
| 19 | + </ul> |
| 20 | + <p>See <a href="https://nullsignal.games/players/supported-formats/">Null Signal Games' Supported Formats page</a> for more information.</p> |
| 21 | + </div> |
| 22 | + <hr> |
| 23 | + <div id="restrictions-root" role="tabpanel" style="display: none;"> |
| 24 | + <!-- Nav tabs --> |
| 25 | + <ul class="nav nav-pills nav-justified" role="tablist" style="margin-bottom:20px"> |
| 26 | + <li role="presentation"><a href="#tab-pane-startup" role="tab" data-toggle="tab">Startup</a></li> |
| 27 | + <li role="presentation" class="active"><a href="#tab-pane-standard" role="tab" data-toggle="tab">Standard</a></li> |
| 28 | + <li role="presentation"><a href="#tab-pane-eternal" role="tab" data-toggle="tab">Eternal</a></li> |
| 29 | + </ul> |
| 30 | + <hr> |
| 31 | + <!-- Tab panes --> |
| 32 | + <div class="tab-content"> |
| 33 | + <div role="tabpanel" class="tab-pane" id="tab-pane-startup"></div> |
| 34 | + <div role="tabpanel" class="tab-pane active" id="tab-pane-standard"></div> |
| 35 | + <div role="tabpanel" class="tab-pane" id="tab-pane-eternal"></div> |
| 36 | + </div> |
| 37 | + </div> |
| 38 | + |
| 39 | + <noscript> |
| 40 | + <p class="text-center">Please enable JavaScript to view this page fully.</p> |
| 41 | + <hr> |
| 42 | + </noscript> |
| 43 | +</div> |
| 44 | + |
| 45 | +<script> |
| 46 | +// Takes a list of cards and an object mapping integers to lists of card IDs |
| 47 | +// Returns a new object with the cards in place of their IDs |
| 48 | +function makeCardMap(cards, obj) { |
| 49 | + return Object.keys(obj).reduce(function(newObj, key) { |
| 50 | + newObj[key] = cards.filter(card => obj[key].includes(card.id)); |
| 51 | + return newObj |
| 52 | + }, {}); |
| 53 | +} |
| 54 | +
|
| 55 | +// Creates a list of cards with the given header |
| 56 | +function generateList(header, cards, pre='') { |
| 57 | + if (pre.length > 0) { |
| 58 | + pre = `<li>${pre}</li>`; |
| 59 | + } |
| 60 | + return cards.reduce((text, card) => { |
| 61 | + return text + `<li><a href="${cardToLatestPrintingLink(card)}">${card.attributes.title}</a></li>`; |
| 62 | + }, `<li><strong>${header}</strong><ul>${pre}`) + '</ul></li>'; |
| 63 | +} |
| 64 | +
|
| 65 | +async function buildBanlistsView() { |
| 66 | + // Unhide the restrictions view (accounting for browsers with JS disabled) |
| 67 | + $('#restrictions-root').show(); |
| 68 | +
|
| 69 | + // Add a temporary loading indicator |
| 70 | + $('.tab-pane').append(loading_icon); |
| 71 | +
|
| 72 | + // Load data from API |
| 73 | + const desiredFormats = ['startup', 'standard', 'eternal']; |
| 74 | + const [formats, cards, restrictions] = await Promise.all([ |
| 75 | + fetchData(`{{ v3_api_url }}/api/v3/public/formats`).then(fs => fs.filter(f => desiredFormats.includes(f.id))), |
| 76 | + fetchCards(`?include=card_subtypes&filter[search]=in_restriction:true`, 250).then(splitBySide), |
| 77 | + fetchData(`{{ v3_api_url }}/api/v3/public/restrictions?sort=-date_start`) |
| 78 | + ]); |
| 79 | +
|
| 80 | + // Remove the loading indicator |
| 81 | + $('.temp-loading').remove(); |
| 82 | +
|
| 83 | + // Add each format to the page |
| 84 | + formats.forEach(f => { |
| 85 | + const jqPane = $(`#tab-pane-${f.id}`); |
| 86 | + const formatRestrictions = restrictions.filter(r => f.attributes.restriction_ids.includes(r.id)); |
| 87 | + if (formatRestrictions.length == 0) { |
| 88 | + jqPane.append(`<p>No cards are currently banned in ${f.attributes.name}. Have a blast!</p>`); |
| 89 | + } else { |
| 90 | + const panelList = new PanelList(jqPane, null, false, "toggle", "search"); |
| 91 | + formatRestrictions.forEach(r => { |
| 92 | + const active = r.id == f.attributes.active_restriction_id; |
| 93 | + const visible = active || r.id == formatRestrictions[0].id; |
| 94 | +
|
| 95 | + // Create panel |
| 96 | + const panel = panelList.createPanel(r.id, visible); |
| 97 | + panel.addHeader(r.attributes.name); |
| 98 | + panel.addSubheader(`<a href=${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':`b!${r.id.replaceAll('_', '-')}`})}>${r.attributes.size} cards</a>. Start Date: ${r.attributes.date_start}.`); |
| 99 | +
|
| 100 | + // Get content data |
| 101 | + const v = r.attributes.verdicts; |
| 102 | + // Lists (bans, restricted cards, global penalty cards) |
| 103 | + const [corpBan, runnerBan] = cards.map(cs => cs.filter(card => v.banned?.includes(card.id))); |
| 104 | + const [corpRes, runnerRes] = cards.map(cs => cs.filter(card => v.restricted?.includes(card.id))); |
| 105 | + const [corpPen, runnerPen] = cards.map(cs => cs.filter(card => v.global_penalty?.includes(card.id))); |
| 106 | + // Mappings (points, universal faction costs) |
| 107 | + const [corpUFC, runnerUFC] = cards.map(cs => makeCardMap(cs, v.universal_faction_cost)); |
| 108 | + const [corpPts, runnerPts] = cards.map(cs => makeCardMap(cs, v.points)); |
| 109 | +
|
| 110 | + // Add body |
| 111 | + panel.addBody(); |
| 112 | + panel.addBodyContent(`<div class="container-fluid"><div class="row flex-fill"><div class="col-md-6"><h3>Corp Cards</h3><ul class="corp"></ul></div><div class="col-md-6"><h3>Runner Cards</h3><ul class="runner"></ul></div></div></div>`); |
| 113 | +
|
| 114 | + // Generate corp restrictions |
| 115 | + const jqCorp = panel.body.find('ul.corp'); |
| 116 | + // Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length) |
| 117 | + if (corpBan.length > 0) { |
| 118 | + if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents |
| 119 | + const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:corp'})}">Current</a></strong> subtype.`; |
| 120 | + jqCorp.append(generateList('Banned', removeCurrents(corpBan), pre)); |
| 121 | + } else { |
| 122 | + jqCorp.append(generateList('Banned', corpBan)); |
| 123 | + } |
| 124 | + } |
| 125 | + // The others |
| 126 | + if (corpRes.length > 0) { jqCorp.append(generateList('Restricted', corpRes)); } |
| 127 | + Object.keys(corpUFC).sort().reverse().forEach(p => { jqCorp.append(generateList(`+${p} Universal Influence`, corpUFC[p])); }); |
| 128 | + if (corpPen.length > 0) { jqCorp.append(generateList('Identity Influence Reduction', corpPen)); } |
| 129 | + Object.keys(corpPts).sort().reverse().forEach(p => { jqCorp.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, corpPts[p])); }); |
| 130 | +
|
| 131 | + // Generate runner restrictions |
| 132 | + const jqRunner = panel.body.find('ul.runner'); |
| 133 | + // Bans (banned subtypes (i.e. currents) are removed beforehand to reduce length) |
| 134 | + if (runnerBan.length > 0) { |
| 135 | + if (r.attributes.banned_subtypes.length > 0) { // NOTE: currently hardcoded to only be currents |
| 136 | + const pre = `All cards with the <strong><a href="${Routing.generate('cards_find', {type:'find', 'view':'list', 'q':'s:current d:runner'})}">Current</a></strong> subtype.`; |
| 137 | + jqRunner.append(generateList('Banned', removeCurrents(runnerBan), pre)); |
| 138 | + } else { |
| 139 | + jqRunner.append(generateList('Banned', runnerBan)); |
| 140 | + } |
| 141 | + } |
| 142 | + // The others |
| 143 | + if (runnerRes.length > 0) { jqRunner.append(generateList('Restricted', runnerRes)); } |
| 144 | + Object.keys(runnerUFC).sort().reverse().forEach(p => { jqRunner.append(generateList(`+${p} Universal Influence`, runnerUFC[p])); }); |
| 145 | + if (runnerPen.length > 0) { jqRunner.append(generateList('Identity Influence Reduction', runnerPen)); } |
| 146 | + Object.keys(runnerPts).sort().reverse().forEach(p => { jqRunner.append(generateList(`${p} ${p == 1 ? 'Point' : 'Points'}`, runnerPts[p])); }); |
| 147 | + }); |
| 148 | + } |
| 149 | + }); |
| 150 | +} |
| 151 | +
|
| 152 | +// Create the banlists view on load |
| 153 | +buildBanlistsView(); |
| 154 | +</script> |
| 155 | +
|
| 156 | +{% endblock %} |
0 commit comments