mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-08-20 10:02:12 -05:00
Add upcoming cards
Upcoming cards are identified by a negative number. The pre-release number will be preserved as an alt number after release to avoid breaking saved deck and replay data that references it.
This commit is contained in:
@@ -287,6 +287,7 @@ class Board {
|
||||
clearChildren(this.table);
|
||||
this.cells.splice(0);
|
||||
this.highlightedCells.splice(0);
|
||||
this.animatedCells.splice(0);
|
||||
this.specialAnimatedCells.splice(0);
|
||||
|
||||
const boardWidth = this.grid.length;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Card {
|
||||
number: number;
|
||||
altNumber: number | null;
|
||||
name: string;
|
||||
rarity: Rarity;
|
||||
specialCost: number;
|
||||
@@ -11,8 +12,9 @@ class Card {
|
||||
private maxX: number;
|
||||
private maxY: number;
|
||||
|
||||
constructor(number: number, name: string, rarity: Rarity, specialCost: number, grid: Space[][]) {
|
||||
constructor(number: number, altNumber: number | null, name: string, rarity: Rarity, specialCost: number, grid: Space[][]) {
|
||||
this.number = number;
|
||||
this.altNumber = altNumber;
|
||||
this.name = name;
|
||||
this.rarity = rarity;
|
||||
this.specialCost = specialCost;
|
||||
@@ -38,9 +40,11 @@ class Card {
|
||||
}
|
||||
|
||||
static fromJson(obj: any) {
|
||||
return new Card(obj.number, obj.name, obj.rarity, obj.specialCost, obj.grid);
|
||||
return new Card(obj.number, obj.altNumber ?? null, obj.name, obj.rarity, obj.specialCost, obj.grid);
|
||||
}
|
||||
|
||||
get isUpcoming() { return this.number < 0; }
|
||||
|
||||
getSpace(x: number, y: number, rotation: number) {
|
||||
switch (rotation & 3) {
|
||||
case 0: return this.grid[x][y];
|
||||
|
||||
@@ -10,6 +10,7 @@ class CardButton extends CheckButton {
|
||||
button.type = 'button';
|
||||
button.classList.add('card');
|
||||
button.classList.add([ 'common', 'rare', 'fresh' ][card.rarity]);
|
||||
if (card.number < 0) button.classList.add('upcoming');
|
||||
button.dataset.cardNumber = card.number.toString();
|
||||
super(button);
|
||||
|
||||
@@ -40,7 +41,7 @@ class CardButton extends CheckButton {
|
||||
|
||||
let el2 = document.createElement('div');
|
||||
el2.classList.add('cardNumber');
|
||||
el2.innerText = card.number.toString();
|
||||
el2.innerText = card.number >= 0 ? `No. ${card.number}` : 'Upcoming';
|
||||
row.appendChild(el2);
|
||||
|
||||
el2 = document.createElement('div');
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
const cardDatabase = {
|
||||
cards: null as Card[] | null,
|
||||
lastOfficialCardNumber: 0,
|
||||
_byAltNumber: [ ] as Card[],
|
||||
|
||||
// Upcoming cards are identified with a negative number, as their actual numbers aren't known until their release.
|
||||
// The placeholder numbers will be kept as alternate numbers then, to avoid breaking replay and saved deck data that uses them.
|
||||
get(number: number) {
|
||||
if (cardDatabase.cards == null) throw new Error('Card database not loaded');
|
||||
if (number <= 0 || number > cardDatabase.cards.length) throw new RangeError(`No card with number ${number}`);
|
||||
return cardDatabase.cards[number - 1];
|
||||
if (number > 0) {
|
||||
number--;
|
||||
if (number < cardDatabase.lastOfficialCardNumber) return cardDatabase.cards[number];
|
||||
} else if (number < 0) {
|
||||
const card = cardDatabase._byAltNumber[-number];
|
||||
if (card) return card;
|
||||
}
|
||||
throw new RangeError(`No card with number ${number}`);
|
||||
},
|
||||
isValidCardNumber(number: number) {
|
||||
return number > 0 ? number <= cardDatabase.lastOfficialCardNumber : cardDatabase._byAltNumber[-number] != undefined;
|
||||
},
|
||||
loadAsync() {
|
||||
return new Promise<Card[]>((resolve, reject) => {
|
||||
@@ -19,7 +33,11 @@ const cardDatabase = {
|
||||
const s = cardListRequest.responseText;
|
||||
const response = JSON.parse(s) as object[];
|
||||
for (const o of response) {
|
||||
cards.push(Card.fromJson(o));
|
||||
const card = Card.fromJson(o);
|
||||
cards.push(card);
|
||||
cardDatabase.lastOfficialCardNumber = Math.max(cardDatabase.lastOfficialCardNumber, card.number);
|
||||
if (card.number < 0) cardDatabase._byAltNumber[-card.number] = card;
|
||||
else if (card.altNumber != null && card.altNumber < 0) cardDatabase._byAltNumber[-card.altNumber] = card;
|
||||
}
|
||||
cardDatabase.cards = cards;
|
||||
resolve(cards);
|
||||
|
||||
@@ -4,11 +4,11 @@ class CardList {
|
||||
readonly filterBox: HTMLInputElement;
|
||||
readonly cardButtons: CardButton[] = [ ];
|
||||
|
||||
static readonly cardSortOrders: { [key: string]: ((a: Card, b: Card) => number) | undefined } = {
|
||||
'number': (a, b) => a.number - b.number,
|
||||
static readonly cardSortOrders: { [key: string]: (a: Card, b: Card) => number } = {
|
||||
'number': (a, b) => compareCardNumbers(a, b),
|
||||
'name': (a, b) => a.name.localeCompare(b.name),
|
||||
'size': (a, b) => a.size != b.size ? a.size - b.size : a.number - b.number,
|
||||
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : a.number - b.number,
|
||||
'size': (a, b) => a.size != b.size ? a.size - b.size : compareCardNumbers(a, b),
|
||||
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : compareCardNumbers(a, b),
|
||||
}
|
||||
|
||||
constructor(listElement: HTMLElement, sortBox: HTMLSelectElement, filterBox: HTMLInputElement) {
|
||||
@@ -55,3 +55,7 @@ class CardList {
|
||||
button.buttonElement.hidden = false;
|
||||
}
|
||||
}
|
||||
function compareCardNumbers(a: Card, b: Card) {
|
||||
// Sort upcoming cards after released cards.
|
||||
return a.number >= 0 ? (b.number >= 0 ? a.number - b.number : -1) : (b.number >= 0 ? 1 : b.number - a.number);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class Deck {
|
||||
if (!cardDatabase.cards) throw new Error('Card database must be loaded to validate decks.');
|
||||
if (this.cards.length != 15) return false;
|
||||
for (let i = 0; i < 15; i++) {
|
||||
if (this.cards[i] <= 0 || this.cards[i] > cardDatabase.cards.length) return false;
|
||||
if (!cardDatabase.isValidCardNumber(this.cards[i])) return false;
|
||||
if (this.cards.indexOf(this.cards[i], i + 1) >= 0) return false; // Duplicate cards
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -68,9 +68,8 @@ function deckEditInitStageDatabase(stages: Stage[]) {
|
||||
}
|
||||
} else if (selectedDeck) {
|
||||
for (const cardNumber of selectedDeck.cards) {
|
||||
if (cardNumber > 0 && cardNumber <= cardDatabase.cards!.length) {
|
||||
if (cardDatabase.isValidCardNumber(cardNumber))
|
||||
addTestDeckCard(cardDatabase.get(cardNumber));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +104,12 @@ function editDeck() {
|
||||
showPage('deckEdit');
|
||||
}
|
||||
|
||||
function createDeckEditCardButton(index: number, card: number) {
|
||||
const button = new CardButton(cardDatabase.cards![card - 1]);
|
||||
function createDeckEditCardButton(index: number, cardNumber: number) {
|
||||
const button = new CardButton(cardDatabase.get(cardNumber));
|
||||
button.buttonElement.addEventListener('click', () => {
|
||||
selectedDeckCardIndex = index;
|
||||
for (const button2 of cardList.cardButtons) {
|
||||
button2.checked = button2.card.number == card;
|
||||
button2.checked = button2.card.number == cardNumber;
|
||||
}
|
||||
cardList.listElement.parentElement!.classList.add('selecting');
|
||||
});
|
||||
|
||||
@@ -158,7 +158,7 @@ deckImportForm.addEventListener('submit', e => {
|
||||
const data = JSON.parse(deckImportTextBox.value);
|
||||
const decks = (data instanceof Array ? data : [ data ]) as Deck[];
|
||||
for (const deck of decks) {
|
||||
if (typeof(deck) != 'object' || !Array.isArray(deck.cards) || deck.cards.length != 15 || deck.cards.find(i => i < 0 || i > cardDatabase.cards!.length))
|
||||
if (typeof(deck) != 'object' || !Array.isArray(deck.cards) || deck.cards.length != 15 || deck.cards.find(i => !cardDatabase.isValidCardNumber(i)))
|
||||
throw new SyntaxError('Invalid JSON deck');
|
||||
}
|
||||
importDecks(decks);
|
||||
@@ -187,7 +187,7 @@ function selectDeck() {
|
||||
deckNameLabel.innerText = selectedDeck.name;
|
||||
for (const cardNumber of selectedDeck.cards) {
|
||||
if (cardNumber) {
|
||||
const card = cardDatabase.cards![cardNumber - 1];
|
||||
const card = cardDatabase.get(cardNumber);
|
||||
size += card.size;
|
||||
|
||||
const button = new CardButton(card);
|
||||
|
||||
@@ -38,7 +38,7 @@ function loadReplay(base64: string) {
|
||||
const initialDrawOrder = [ ];
|
||||
const drawOrder = [ ];
|
||||
for (let j = 0; j < 15; j++) {
|
||||
deck.push(cardDatabase.get(dataView.getUint8(pos + 9 + j)));
|
||||
deck.push(loadCardFromReplay(dataView, pos + 9 + j));
|
||||
}
|
||||
for (let j = 0; j < 2; j++) {
|
||||
initialDrawOrder.push(dataView.getUint8(pos + 24 + j) & 0xF);
|
||||
@@ -59,14 +59,14 @@ function loadReplay(base64: string) {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const turn = [ ];
|
||||
for (let j = 0; j < numPlayers; j++) {
|
||||
const cardNumber = dataView.getUint8(pos);
|
||||
const card = loadCardFromReplay(dataView, pos);
|
||||
const b = dataView.getUint8(pos + 1);
|
||||
const x = dataView.getInt8(pos + 2);
|
||||
const y = dataView.getInt8(pos + 3);
|
||||
if (b & 0x80)
|
||||
turn.push({ card: cardDatabase.get(cardNumber), isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
turn.push({ card, isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
else {
|
||||
const move: PlayMove = { card: cardDatabase.get(cardNumber), isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
const move: PlayMove = { card, isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
turn.push(move);
|
||||
}
|
||||
pos += 4;
|
||||
@@ -113,7 +113,7 @@ function loadReplay(base64: string) {
|
||||
const drawOrder = [ ];
|
||||
let won = false;
|
||||
for (let j = 0; j < 15; j++) {
|
||||
deck.push(cardDatabase.get(dataView.getUint8(pos + j)));
|
||||
deck.push(loadCardFromReplay(dataView, pos + j));
|
||||
}
|
||||
for (let j = 0; j < 2; j++) {
|
||||
initialDrawOrder.push(dataView.getUint8(pos + 15 + j) & 0xF);
|
||||
@@ -162,14 +162,14 @@ function replayLoadTurns(dataView: DataView, numPlayers: number, pos: number) {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const turn = [ ];
|
||||
for (let j = 0; j < numPlayers; j++) {
|
||||
const cardNumber = dataView.getUint8(pos);
|
||||
const card = loadCardFromReplay(dataView, pos);
|
||||
const b = dataView.getUint8(pos + 1);
|
||||
const x = dataView.getInt8(pos + 2);
|
||||
const y = dataView.getInt8(pos + 3);
|
||||
if (b & 0x80)
|
||||
turn.push({ card: cardDatabase.get(cardNumber), isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
turn.push({ card, isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
else {
|
||||
const move: PlayMove = { card: cardDatabase.get(cardNumber), isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
const move: PlayMove = { card, isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
turn.push(move);
|
||||
}
|
||||
pos += 4;
|
||||
@@ -178,3 +178,5 @@ function replayLoadTurns(dataView: DataView, numPlayers: number, pos: number) {
|
||||
}
|
||||
return turns;
|
||||
}
|
||||
|
||||
function loadCardFromReplay(dataView: DataView, index: number) { return cardDatabase.get(dataView.getUint8(index) > cardDatabase.lastOfficialCardNumber ? dataView.getInt8(index) : dataView.getUint8(index)); }
|
||||
|
||||
@@ -347,6 +347,7 @@ function setupWebSocket(gameID: string) {
|
||||
|
||||
const move = payload.data.moves[i];
|
||||
const button = new CardButton(move.card);
|
||||
button.buttonElement.disabled = true;
|
||||
if (move.isSpecialAttack) {
|
||||
anySpecialAttacks = true;
|
||||
button.buttonElement.classList.add('specialAttack');
|
||||
|
||||
@@ -306,6 +306,8 @@ dialog::backdrop {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:is(#deckCardListView, #deckCardListEdit, .cardListGrid) .card.upcoming { background: midnightblue; }
|
||||
|
||||
.card.common { --colour: rgb(89, 49, 255); }
|
||||
.card.rare { --colour: rgb(231, 180, 39); }
|
||||
.card.fresh { --colour: white; }
|
||||
@@ -323,12 +325,25 @@ dialog::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cardListGrid .card:hover .cardNumber {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: grey;
|
||||
border: 1px solid black;
|
||||
top: -1.5em;
|
||||
left: -1em;
|
||||
padding: 0 1em;
|
||||
transform: rotate(-10deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cardName {
|
||||
text-align: center;
|
||||
line-height: 1.25em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.card:is([data-card-number="163"], [data-card-number="166"], [data-card-number="196"], [data-card-number="197"], [data-card-number="199"], [data-card-number="202"]) .cardName {
|
||||
.card:is([data-card-number="163"], [data-card-number="166"], [data-card-number="196"], [data-card-number="197"], [data-card-number="199"],
|
||||
[data-card-number="202"], [data-card-number="-3"], [data-card-number="-5"], [data-card-number="-12"]) .cardName {
|
||||
position: absolute;
|
||||
left: -1em;
|
||||
right: -1em;
|
||||
@@ -415,10 +430,14 @@ dialog::backdrop {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0.5em;
|
||||
opacity: 0.25;
|
||||
z-index: 0;
|
||||
}
|
||||
.card:is(:hover, :focus-within):not(.checked, .disabled)::before {
|
||||
.card:is(.checked, :hover, :focus-within):not([disabled]) {
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
.card:is(:hover, :focus-within):not(.checked, .disabled, [disabled])::before {
|
||||
content: '';
|
||||
background: grey;
|
||||
}
|
||||
@@ -1540,7 +1559,7 @@ dialog::backdrop {
|
||||
.playerBar[data-index="1"] { grid-column: 1 / span 4; grid-row: 1; }
|
||||
.playerBar[data-index="2"] { grid-column: 1 / span 4; grid-row: 3; }
|
||||
.playerBar[data-index="3"] { grid-column: 1 / span 4; grid-row: 1; }
|
||||
|
||||
|
||||
.wins { margin-right: 8em; }
|
||||
|
||||
#gamePage.boardFlipped .playerBar[data-index="3"] { grid-column: 1 / span 4; grid-row: 3; }
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace TableturfBattleServer;
|
||||
public class Card {
|
||||
[JsonProperty("number")]
|
||||
public int Number { get; }
|
||||
[JsonProperty("altNumber")]
|
||||
public int? AltNumber { get; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; }
|
||||
[JsonProperty("rarity")]
|
||||
@@ -16,9 +18,12 @@ public class Card {
|
||||
[JsonProperty("grid")]
|
||||
private readonly Space[,] grid;
|
||||
|
||||
internal Card(int number, string name, Rarity rarity, Space[,] grid) : this(number, name, rarity, null, grid) { }
|
||||
internal Card(int number, string name, Rarity rarity, int? specialCost, Space[,] grid) {
|
||||
internal Card(int number, string name, Rarity rarity, Space[,] grid) : this(number, null, name, rarity, null, grid) { }
|
||||
internal Card(int number, int? altNumber, string name, Rarity rarity, Space[,] grid) : this(number, altNumber, name, rarity, null, grid) { }
|
||||
internal Card(int number, string name, Rarity rarity, int? specialCost, Space[,] grid) : this(number, null, name, rarity, specialCost, grid) { }
|
||||
internal Card(int number, int? altNumber, string name, Rarity rarity, int? specialCost, Space[,] grid) {
|
||||
this.Number = number;
|
||||
this.AltNumber = altNumber;
|
||||
this.Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.Rarity = rarity;
|
||||
this.grid = grid ?? throw new ArgumentNullException(nameof(grid));
|
||||
|
||||
@@ -2097,21 +2097,158 @@ public static class CardDatabase {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
|
||||
// Upcoming Drizzle Season 2023 cards
|
||||
new(-1, "Custom Goo Tuber", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, I, 0, 0, 0 },
|
||||
{ 0, 0, I, S, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-2, "Sloshing Machine Neo", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, I, 0, 0, 0 },
|
||||
{ 0, 0, S, I, I, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-3, "Heavy Edit Splatling", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, I, I, 0, 0 },
|
||||
{ 0, 0, 0, I, I, I, 0, 0 },
|
||||
{ 0, 0, 0, I, I, S, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, I, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-4, "Dread Wringer", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, I, 0, 0 },
|
||||
{ 0, 0, I, I, I, I, 0, 0 },
|
||||
{ 0, 0, I, I, I, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, S, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-5, "Octobrush Nouveau", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, S, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, I, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-6, "Gold Dynamo Roller", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, S, I, I, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-7, "Tri-Stringer Collab", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, I, 0, 0, 0, 0, 0 },
|
||||
{ 0, S, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, I, I, I, I, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-8, "Bloblobber Deco", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, I, 0, 0, 0 },
|
||||
{ 0, 0, I, S, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-9, "Horrorboros", Rarity.Fresh, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, S, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, I, I, 0, I, I, I, 0 },
|
||||
{ I, 0, I, 0, I, 0, I, 0 },
|
||||
{ 0, 0, I, I, I, 0, I, 0 },
|
||||
{ 0, 0, 0, 0, 0, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-10, "Cohozuna", Rarity.Fresh, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, I, 0, I, I, 0, 0, 0 },
|
||||
{ 0, I, I, I, I, I, 0, 0 },
|
||||
{ 0, 0, I, I, I, I, 0, 0 },
|
||||
{ 0, 0, 0, I, I, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, I, 0, 0 },
|
||||
{ 0, 0, 0, 0, S, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-11, "Sorella Brella", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, I, I, I, 0, 0, 0 },
|
||||
{ 0, I, I, S, I, I, 0, 0 },
|
||||
{ 0, 0, 0, I, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}),
|
||||
new(-12, "Ballpoint Splatling Nouveau", Rarity.Common, new Space[,] {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, S, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, I, 0, 0, 0 },
|
||||
{ 0, 0, I, I, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, I, I, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
})
|
||||
};
|
||||
|
||||
public static Version Version { get; } = new(4, 1, 0, 0);
|
||||
public static DateTime LastModified { get; } = new(2023, 8, 12, 9, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly Dictionary<int, Card> byAltNumber;
|
||||
|
||||
public static int LastOfficialCardNumber { get; }
|
||||
|
||||
public static Version Version { get; } = new(5, 0, 0, 0);
|
||||
public static DateTime LastModified { get; } = new(2023, 8, 18, 1, 0, 0, DateTimeKind.Utc);
|
||||
public static string JSON { get; }
|
||||
public static ReadOnlyCollection<Card> Cards { get; }
|
||||
|
||||
static CardDatabase() {
|
||||
Cards = Array.AsReadOnly(cards);
|
||||
JSON = JsonConvert.SerializeObject(cards);
|
||||
LastOfficialCardNumber = cards.Last(c => c.Number > 0).Number;
|
||||
byAltNumber = new(from c in cards where c.Number < 0 || c.AltNumber is not null select new KeyValuePair<int, Card>(c.Number < 0 ? c.Number : c.AltNumber ?? 0, c));
|
||||
}
|
||||
|
||||
public static Card GetCard(int number) {
|
||||
number--;
|
||||
return number >= 0 && number < cards.Length ? cards[number] : throw new ArgumentOutOfRangeException(nameof(number));
|
||||
}
|
||||
// Upcoming cards are identified with a negative number, as their actual numbers aren't known until their release.
|
||||
// The placeholder numbers will be kept as alternate numbers then, to avoid breaking replay and saved deck data that uses them.
|
||||
public static Card GetCard(int number) => number switch {
|
||||
> 0 => --number < LastOfficialCardNumber ? cards[number] : throw new ArgumentOutOfRangeException(nameof(number)),
|
||||
< 0 => byAltNumber.TryGetValue(number, out var card) ? card : throw new ArgumentOutOfRangeException(nameof(number)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(number))
|
||||
};
|
||||
|
||||
public static bool IsValidCardNumber(int number) => number switch {
|
||||
> 0 => number <= LastOfficialCardNumber,
|
||||
< 0 => byAltNumber.ContainsKey(number),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ internal class Program {
|
||||
}
|
||||
var cards = new int[15];
|
||||
for (int i = 0; i < 15; i++) {
|
||||
if (!int.TryParse(array[i], out var cardNumber) || cardNumber < 0 || cardNumber > CardDatabase.Cards.Count) {
|
||||
if (!int.TryParse(array[i], out var cardNumber) || !CardDatabase.IsValidCardNumber(cardNumber)) {
|
||||
SetErrorResponse(e.Response, new(HttpStatusCode.UnprocessableEntity, "InvalidDeckCards", "Invalid deck list."));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user