@flesh-and-blood/search 3.9.13 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,395 @@
1
+ import { Format, Hero, Talent } from "@flesh-and-blood/types";
2
+ import { PUNCTUATION } from "./constants";
3
+ var FilterProperty = /* @__PURE__ */ ((FilterProperty2) => {
4
+ FilterProperty2["BannedFormats"] = "bannedFormats";
5
+ FilterProperty2["LegalFormats"] = "legalFormats";
6
+ FilterProperty2["LegalHeroes"] = "legalHeroes";
7
+ return FilterProperty2;
8
+ })(FilterProperty || {});
9
+ const oneToFifty = Array.from(Array(50).keys()).map((value) => `${value}`);
10
+ const nicknameFormatMappings = [
11
+ {
12
+ format: Format.ClassicConstructed,
13
+ nicknames: ["cc", "classic"]
14
+ },
15
+ {
16
+ format: Format.LivingLegend,
17
+ nicknames: [
18
+ "cc ll",
19
+ "classic constructed ll",
20
+ "ll cc",
21
+ "ll",
22
+ "living legend"
23
+ ]
24
+ },
25
+ {
26
+ format: Format.SilverAge,
27
+ nicknames: ["sage"]
28
+ },
29
+ {
30
+ format: Format.GoldenAge,
31
+ nicknames: ["gage"]
32
+ },
33
+ {
34
+ format: Format.UltimatePitFight,
35
+ nicknames: ["upf"]
36
+ }
37
+ ];
38
+ const formatMappings = Object.values(Format).map((format) => {
39
+ const withNicknames = nicknameFormatMappings.find(
40
+ ({ format: nicknameFormat }) => nicknameFormat === format
41
+ );
42
+ const cleanFormat = format.toLowerCase().replaceAll(PUNCTUATION, "");
43
+ return withNicknames ? { ...withNicknames, format: cleanFormat } : { format: cleanFormat };
44
+ });
45
+ const nicknameHeroMappings = [
46
+ {
47
+ hero: Hero.DataDoll,
48
+ nicknames: ["data", "datadoll"]
49
+ },
50
+ {
51
+ hero: Hero.Dorinthea,
52
+ nicknames: ["dori"]
53
+ },
54
+ {
55
+ hero: Hero.Genis,
56
+ nicknames: ["genis"]
57
+ },
58
+ {
59
+ hero: Hero.GravyBones,
60
+ nicknames: ["gravy"]
61
+ },
62
+ {
63
+ hero: Hero.Iyslander,
64
+ nicknames: ["islander"]
65
+ }
66
+ ];
67
+ const heroMappings = Object.values(
68
+ Hero
69
+ ).map((hero) => {
70
+ const withNicknames = nicknameHeroMappings.find(
71
+ ({ hero: nicknameHero }) => nicknameHero === hero
72
+ );
73
+ const cleanHero = hero.toLowerCase().replaceAll(PUNCTUATION, "");
74
+ return withNicknames ? { ...withNicknames, hero: cleanHero } : { hero: cleanHero };
75
+ });
76
+ const rankedRarity = [
77
+ "common",
78
+ "rare",
79
+ "super rare",
80
+ "majestic",
81
+ "legendary",
82
+ "fabled"
83
+ ];
84
+ const getRarityFilter = (values, modifier, isExcluded, isOptional) => {
85
+ const rarities = [];
86
+ if (!modifier) {
87
+ rarities.push(...values);
88
+ } else {
89
+ for (const value of values) {
90
+ switch (modifier) {
91
+ case ">=":
92
+ let start = false;
93
+ for (const rarity of rankedRarity) {
94
+ if (start) {
95
+ rarities.push(rarity);
96
+ } else if (rarity === value) {
97
+ start = true;
98
+ rarities.push(rarity);
99
+ }
100
+ }
101
+ break;
102
+ case ">":
103
+ let start2 = false;
104
+ for (const rarity of rankedRarity) {
105
+ if (start2) {
106
+ rarities.push(rarity);
107
+ } else if (rarity === value) {
108
+ start2 = true;
109
+ }
110
+ }
111
+ break;
112
+ case "<=":
113
+ let start3 = false;
114
+ for (const rarity of rankedRarity.slice().reverse()) {
115
+ if (start3) {
116
+ rarities.push(rarity);
117
+ } else if (rarity === value) {
118
+ start3 = true;
119
+ rarities.push(rarity);
120
+ }
121
+ }
122
+ break;
123
+ case "<":
124
+ let start4 = false;
125
+ for (const rarity of rankedRarity.slice().reverse()) {
126
+ if (start4) {
127
+ rarities.push(rarity);
128
+ } else if (rarity === value) {
129
+ start4 = true;
130
+ }
131
+ }
132
+ break;
133
+ default:
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ return {
139
+ filterToPropertyMapping: {
140
+ nestedProperty: "rarity",
141
+ property: "printings",
142
+ isArray: true
143
+ },
144
+ isExcluded,
145
+ isOptional,
146
+ isOr: true,
147
+ values: rarities
148
+ };
149
+ };
150
+ const getLegalFilters = (values, isExcluded, isOptional, additionalHeroes, filterPropertyOverride) => {
151
+ const cleanAdditionalHeroes = additionalHeroes.map((hero) => ({
152
+ hero: hero.toLowerCase().replaceAll(PUNCTUATION, "")
153
+ }));
154
+ const filters = [];
155
+ const formats = [];
156
+ const heroes = [];
157
+ for (const value of values) {
158
+ const matchingFormat = formatMappings.find(({ format, nicknames }) => {
159
+ const isAMatch = format === value || !!nicknames && nicknames.includes(value);
160
+ return isAMatch;
161
+ });
162
+ if (matchingFormat) {
163
+ formats.push(matchingFormat.format);
164
+ } else {
165
+ const matchingHero = heroMappings.find(({ hero, nicknames }) => {
166
+ const isAMatch = hero === value || !!nicknames && nicknames.includes(value);
167
+ return isAMatch;
168
+ }) || cleanAdditionalHeroes.find(({ hero }) => hero === value);
169
+ if (matchingHero) {
170
+ heroes.push(matchingHero.hero);
171
+ }
172
+ }
173
+ }
174
+ const filterProperty = filterPropertyOverride || "legalFormats";
175
+ if (formats.length > 0) {
176
+ filters.push({
177
+ filterToPropertyMapping: { property: filterProperty, isArray: true },
178
+ values: formats,
179
+ isOr: true,
180
+ isExcluded,
181
+ isOptional
182
+ });
183
+ }
184
+ if (heroes.length > 0) {
185
+ filters.push({
186
+ filterToPropertyMapping: {
187
+ property: "legalHeroes" /* LegalHeroes */,
188
+ isArray: true
189
+ },
190
+ values: heroes,
191
+ isOr: true,
192
+ isExcluded,
193
+ isOptional
194
+ });
195
+ }
196
+ return filters;
197
+ };
198
+ const getBannedFilters = (values, isExcluded, isOptional, additionalHeroes) => {
199
+ return getLegalFilters(
200
+ values,
201
+ isExcluded,
202
+ isOptional,
203
+ additionalHeroes,
204
+ "bannedFormats"
205
+ );
206
+ };
207
+ const getMetaFilters = (isExcluded, isOptional, filterKey, values, modifier, additionalHeroes) => {
208
+ const filters = [];
209
+ if (isLegalFilter(filterKey)) {
210
+ filters.push(
211
+ ...getLegalFilters(values, isExcluded, isOptional, additionalHeroes)
212
+ );
213
+ } else if (isBannedFilter(filterKey)) {
214
+ filters.push(
215
+ ...getBannedFilters(values, isExcluded, isOptional, additionalHeroes)
216
+ );
217
+ } else if (isRarityFilter(filterKey)) {
218
+ filters.push(getRarityFilter(values, modifier, isExcluded, isOptional));
219
+ }
220
+ return filters;
221
+ };
222
+ const noCost = [
223
+ {
224
+ filterToPropertyMapping: {
225
+ property: "cost",
226
+ isNumber: true
227
+ },
228
+ isExcluded: true,
229
+ values: oneToFifty
230
+ },
231
+ {
232
+ filterToPropertyMapping: {
233
+ property: "specialCost",
234
+ isString: true,
235
+ partialMatch: true
236
+ },
237
+ isExcluded: true,
238
+ values: ["*", "x"]
239
+ },
240
+ {
241
+ filterToPropertyMapping: {
242
+ property: "types",
243
+ isArray: true,
244
+ partialMatch: true
245
+ },
246
+ isExcluded: true,
247
+ values: ["equipment", "hero", "placeholder", "token", "weapon"]
248
+ }
249
+ ];
250
+ const noDefense = [
251
+ {
252
+ filterToPropertyMapping: {
253
+ property: "defense",
254
+ isNumber: true
255
+ },
256
+ isExcluded: true,
257
+ values: oneToFifty
258
+ },
259
+ {
260
+ filterToPropertyMapping: {
261
+ property: "specialDefense",
262
+ isString: true,
263
+ partialMatch: true
264
+ },
265
+ isExcluded: true,
266
+ values: ["*", "x"]
267
+ },
268
+ {
269
+ filterToPropertyMapping: {
270
+ property: "types",
271
+ isArray: true,
272
+ partialMatch: true
273
+ },
274
+ isExcluded: true,
275
+ values: ["hero", "placeholder", "token", "weapon"]
276
+ }
277
+ ];
278
+ const noPitch = [
279
+ {
280
+ filterToPropertyMapping: {
281
+ property: "pitch",
282
+ isNumber: true
283
+ },
284
+ isExcluded: true,
285
+ values: oneToFifty
286
+ },
287
+ {
288
+ filterToPropertyMapping: {
289
+ property: "types",
290
+ isArray: true,
291
+ partialMatch: true
292
+ },
293
+ isExcluded: true,
294
+ values: ["equipment", "hero", "placeholder", "token", "weapon"]
295
+ },
296
+ {
297
+ filterToPropertyMapping: {
298
+ property: "isCardBack",
299
+ isBoolean: true
300
+ },
301
+ isExcluded: true,
302
+ values: ["true"]
303
+ }
304
+ ];
305
+ const noPower = [
306
+ {
307
+ filterToPropertyMapping: {
308
+ property: "power",
309
+ isNumber: true
310
+ },
311
+ isExcluded: true,
312
+ values: oneToFifty
313
+ },
314
+ {
315
+ filterToPropertyMapping: {
316
+ property: "specialPower",
317
+ isString: true,
318
+ partialMatch: true
319
+ },
320
+ isExcluded: true,
321
+ values: ["*", "x"]
322
+ },
323
+ {
324
+ filterToPropertyMapping: {
325
+ property: "types",
326
+ isArray: true,
327
+ partialMatch: true
328
+ },
329
+ isExcluded: true,
330
+ values: ["equipment", "hero", "placeholder", "token"]
331
+ }
332
+ ];
333
+ const noTalents = [
334
+ {
335
+ filterToPropertyMapping: {
336
+ property: "talents",
337
+ isArray: true
338
+ },
339
+ isExcluded: true,
340
+ values: Object.values(Talent).map((talent) => talent.toLowerCase())
341
+ }
342
+ ];
343
+ const excludedFilters = {
344
+ "!co": noCost,
345
+ "-co": noCost,
346
+ "!cost": noCost,
347
+ "-cost": noCost,
348
+ "!color": noCost,
349
+ "-color": noCost,
350
+ "!b": noDefense,
351
+ "-b": noDefense,
352
+ "!block": noDefense,
353
+ "-block": noDefense,
354
+ "!d": noDefense,
355
+ "-d": noDefense,
356
+ "!def": noDefense,
357
+ "-def": noDefense,
358
+ "!defense": noDefense,
359
+ "-defense": noDefense,
360
+ "!pitch": noPitch,
361
+ "-pitch": noPitch,
362
+ "!p": noPitch,
363
+ "-p": noPitch,
364
+ "!attack": noPower,
365
+ "-attack": noPower,
366
+ "!power": noPower,
367
+ "-power": noPower,
368
+ "!pwr": noPower,
369
+ "-pwr": noPower,
370
+ "!pow": noPower,
371
+ "-pow": noPower,
372
+ "!talents": noTalents,
373
+ "-talents": noTalents,
374
+ "!tal": noTalents,
375
+ "-tal": noTalents
376
+ };
377
+ const getExcludedMetaFilters = (filterKey) => {
378
+ const filters = [];
379
+ const matchingFilters = excludedFilters[filterKey];
380
+ if (matchingFilters) {
381
+ filters.push(...matchingFilters);
382
+ }
383
+ return filters;
384
+ };
385
+ const legalFilters = ["l", "legal", "hero"];
386
+ const isLegalFilter = (filterKey) => legalFilters.includes(filterKey);
387
+ const bannedFilters = ["banned"];
388
+ const isBannedFilter = (filterKey) => bannedFilters.includes(filterKey);
389
+ const rarityFilters = ["r", "rarity"];
390
+ const isRarityFilter = (filterKey) => rarityFilters.includes(filterKey);
391
+ export {
392
+ FilterProperty,
393
+ getExcludedMetaFilters,
394
+ getMetaFilters
395
+ };
@@ -0,0 +1,135 @@
1
+ import { Hero, Trait } from "@flesh-and-blood/types";
2
+ import { PUNCTUATION } from "./constants";
3
+ import { Keyword } from "@flesh-and-blood/types";
4
+ const nameOverrides = {
5
+ "Dawnblade, Resplendent": "Dawnblade"
6
+ };
7
+ const getOverrideOrName = (card) => nameOverrides[card.name] || card.name;
8
+ const getFunctionalTextWithoutSelfReferences = (card) => card.functionalText?.replaceAll(card.name, "");
9
+ const getRelatedCardsByName = (name, cards) => {
10
+ let card = cards.find(
11
+ (card2) => card2.name.toLowerCase().replace(PUNCTUATION, "") === name
12
+ );
13
+ if (!card) {
14
+ card = cards.find(
15
+ (card2) => card2.name.toLowerCase().replace(PUNCTUATION, "").includes(name)
16
+ );
17
+ }
18
+ return getRelatedCards(card, cards);
19
+ };
20
+ const getRelatedCards = (card, availableCards) => {
21
+ const otherPitchMapping = {};
22
+ const referencedByMapping = {};
23
+ const referencesMapping = {};
24
+ if (card) {
25
+ const initialReferencedBy = [];
26
+ const initialReferences = [];
27
+ const cardName = getOverrideOrName(card);
28
+ for (const other of availableCards) {
29
+ const otherName = getOverrideOrName(other);
30
+ const sameName = cardName === otherName;
31
+ const differentCard = card.cardIdentifier !== other.cardIdentifier;
32
+ const differentPitch = card.pitch !== other.pitch;
33
+ if (sameName && differentCard && differentPitch) {
34
+ otherPitchMapping[other.cardIdentifier] = other;
35
+ } else if (!sameName) {
36
+ const isCardSeismicSurge = card.name === "Seismic Surge";
37
+ const isOtherCardHeaved = other.keywords?.includes(Keyword.Heave);
38
+ const isOtherCardHeaveOverride = isCardSeismicSurge && isOtherCardHeaved;
39
+ const isCardMarked = card.name === "Marked";
40
+ const isOtherCardMarking = other.keywords?.includes(Keyword.Mark);
41
+ const isOtherCardMarkOverride = isCardMarked && isOtherCardMarking;
42
+ const otherFunctionalText = getFunctionalTextWithoutSelfReferences(other);
43
+ const cardIsInOtherFunctionalTextOrTraits = otherFunctionalText?.includes(cardName) || card.traits?.some((trait) => otherFunctionalText?.includes(trait));
44
+ const functionalText = getFunctionalTextWithoutSelfReferences(card);
45
+ if (cardIsInOtherFunctionalTextOrTraits || isOtherCardHeaveOverride || isOtherCardMarkOverride) {
46
+ initialReferencedBy.push(other);
47
+ }
48
+ const functionalTextRegex = new RegExp(otherName, "g");
49
+ const functionalTextKeywordRegex = new RegExp(
50
+ `\\*\\*${otherName}\\*\\*`,
51
+ "g"
52
+ );
53
+ const functionalTextReferences = functionalText?.match(functionalTextRegex) || [];
54
+ const functionalTextKeywordReferences = functionalText?.match(functionalTextKeywordRegex) || [];
55
+ const referenceIsKeywordOrFlow = functionalTextReferences.length === functionalTextKeywordReferences.length;
56
+ const otherCardIsInFunctionalText = functionalText?.includes(otherName) && !referenceIsKeywordOrFlow;
57
+ const otherCardTraits = other.traits || [];
58
+ const otherCardIsInTraits = otherCardTraits.some(
59
+ (otherCardTrait) => functionalText?.includes(otherCardTrait)
60
+ );
61
+ const otherCardIsInFunctionalTextOrTraits = otherCardIsInFunctionalText || otherCardIsInTraits;
62
+ const isOtherCardSeismicSurge = other.name === "Seismic Surge";
63
+ const isCardHeaved = card.keywords?.includes(Keyword.Heave);
64
+ const isCardHeaveOverride = isOtherCardSeismicSurge && isCardHeaved;
65
+ const isOtherCardMarked = other.name === "Marked";
66
+ const isCardMarking = card.keywords?.includes(Keyword.Mark);
67
+ const isCardMarkOverride = isOtherCardMarked && isCardMarking;
68
+ if (otherCardIsInFunctionalTextOrTraits || isCardHeaveOverride || isCardMarkOverride) {
69
+ initialReferences.push(other);
70
+ }
71
+ }
72
+ }
73
+ for (const initialReference of initialReferencedBy) {
74
+ referencedByMapping[initialReference.cardIdentifier] = initialReference;
75
+ }
76
+ for (const initialReference of initialReferences) {
77
+ const initialName = initialReference.name;
78
+ if (cardName !== initialName && !initialReferences.some((card2) => {
79
+ const name = card2.name;
80
+ return name !== initialName && name?.includes(initialName);
81
+ })) {
82
+ referencesMapping[initialReference.cardIdentifier] = initialReference;
83
+ }
84
+ }
85
+ }
86
+ const otherPitches = Object.values(otherPitchMapping);
87
+ const referencedBy = Object.values(referencedByMapping);
88
+ const references = Object.values(referencesMapping);
89
+ return { otherPitches, referencedBy, references };
90
+ };
91
+ const CARD_IDENTIFIERS_TO_SKIP = ["cash-in-yellow"];
92
+ const heroReferences = {
93
+ [Hero.Crackni]: { traits: [Trait.AgentOfChaos] },
94
+ [Hero.Maxx]: { tokens: ["hyper-driver"] }
95
+ };
96
+ const getTokensReferencedByCards = (cards, availableTokens, hero) => {
97
+ const referencedTokens = /* @__PURE__ */ new Set();
98
+ for (const card of cards.filter(
99
+ ({ cardIdentifier }) => !CARD_IDENTIFIERS_TO_SKIP.includes(cardIdentifier)
100
+ )) {
101
+ const { references } = getRelatedCards(card, availableTokens);
102
+ for (const token of references.filter((token2) => {
103
+ const isHyperDriver = token2.name === "Hyper Driver";
104
+ const isMaxx = hero === Hero.Maxx;
105
+ return !isHyperDriver || isMaxx;
106
+ })) {
107
+ referencedTokens.add(token);
108
+ }
109
+ }
110
+ if (hero) {
111
+ const heroCards = heroReferences[hero];
112
+ if (heroCards) {
113
+ if (heroCards.cards) {
114
+ for (const card of cards) {
115
+ if (heroCards.cards?.includes(card.cardIdentifier)) {
116
+ referencedTokens.add(card);
117
+ }
118
+ }
119
+ }
120
+ if (heroCards.tokens) {
121
+ for (const token of availableTokens) {
122
+ if (heroCards.tokens?.includes(token.cardIdentifier)) {
123
+ referencedTokens.add(token);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return Array.from(referencedTokens);
130
+ };
131
+ export {
132
+ getRelatedCards,
133
+ getRelatedCardsByName,
134
+ getTokensReferencedByCards
135
+ };