@tf2-automatic/tf2-format 8.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,747 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EconParser = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const parser_1 = require("../parser");
6
+ const TAGS_OF_INTEREST = new Set([
7
+ 'Quality',
8
+ 'Exterior',
9
+ 'Rarity',
10
+ 'Type',
11
+ ]);
12
+ const NON_CRAFTABLE_DESCRIPTIONS = {
13
+ '( Not Usable in Crafting )': true,
14
+ '( Not Tradable, Marketable, or Usable in Crafting )': true,
15
+ '( Not Tradable, Marketable, Usable in Crafting, or Gift Wrappable )': true,
16
+ };
17
+ const WEAR = {
18
+ 'Factory New': 1,
19
+ 'Minimal Wear': 2,
20
+ 'Field-Tested': 3,
21
+ 'Well-Worn': 4,
22
+ 'Battle Scarred': 5,
23
+ };
24
+ const KILLSTREAK_FABRICATORS = {
25
+ 1: 6527,
26
+ 2: 6523,
27
+ 3: 6526,
28
+ };
29
+ const KILLSTREAK_TIERS = {
30
+ Killstreak: 1,
31
+ 'Specialized Killstreak': 2,
32
+ 'Professional Killstreak': 3,
33
+ };
34
+ /* eslint-disable @typescript-eslint/no-duplicate-enum-values */
35
+ var IdentifiableDescription;
36
+ (function (IdentifiableDescription) {
37
+ IdentifiableDescription[IdentifiableDescription["Skip"] = 0] = "Skip";
38
+ IdentifiableDescription[IdentifiableDescription["All"] = 1] = "All";
39
+ // Paints, spells and parts have the same precedence because it has been shown
40
+ // that they may appear in any order.
41
+ IdentifiableDescription[IdentifiableDescription["Paint"] = 1] = "Paint";
42
+ IdentifiableDescription[IdentifiableDescription["Spells"] = 1] = "Spells";
43
+ IdentifiableDescription[IdentifiableDescription["Parts"] = 1] = "Parts";
44
+ IdentifiableDescription[IdentifiableDescription["Effect"] = 2] = "Effect";
45
+ IdentifiableDescription[IdentifiableDescription["Festivized"] = 3] = "Festivized";
46
+ IdentifiableDescription[IdentifiableDescription["Killstreaker"] = 4] = "Killstreaker";
47
+ IdentifiableDescription[IdentifiableDescription["Sheen"] = 5] = "Sheen";
48
+ IdentifiableDescription[IdentifiableDescription["Killstreak"] = 6] = "Killstreak";
49
+ IdentifiableDescription[IdentifiableDescription["Texture"] = 7] = "Texture";
50
+ IdentifiableDescription[IdentifiableDescription["Input"] = 8] = "Input";
51
+ IdentifiableDescription[IdentifiableDescription["Output"] = 9] = "Output";
52
+ IdentifiableDescription[IdentifiableDescription["Target"] = 10] = "Target";
53
+ IdentifiableDescription[IdentifiableDescription["Uses"] = 11] = "Uses";
54
+ IdentifiableDescription[IdentifiableDescription["Craftable"] = 12] = "Craftable";
55
+ })(IdentifiableDescription || (IdentifiableDescription = {}));
56
+ /* eslint-enable @typescript-eslint/no-duplicate-enum-values */
57
+ /**
58
+ * This is a mapping from the item type to the description that we want to
59
+ * start the description parsing from
60
+ */
61
+ var TypeToIdentifiableDescription;
62
+ (function (TypeToIdentifiableDescription) {
63
+ // Cosmetics can have many different descriptions so we will check for all
64
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Cosmetic"] = 1] = "Cosmetic";
65
+ // Taunts may have effects, so we will check for that
66
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Taunt 1"] = 2] = "Taunt 1";
67
+ // Nothing interesting in these items
68
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Party Favor"] = 0] = "Party Favor";
69
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Action"] = 0] = "Action";
70
+ // Most craft items do not have descriptions but we will check for craftable
71
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Craft Item"] = 12] = "Craft Item";
72
+ // Check for uses on limited use items
73
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Festivizer"] = 11] = "Festivizer";
74
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Tool"] = 11] = "Tool";
75
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Gift"] = 11] = "Gift";
76
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Strange Part"] = 11] = "Strange Part";
77
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Usable Item"] = 11] = "Usable Item";
78
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Crate"] = 11] = "Crate";
79
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Unusualifier"] = 9] = "Unusualifier";
80
+ // Start checking for the target
81
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Strangifier"] = 10] = "Strangifier";
82
+ // War paints may have effects
83
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["War Paint"] = 2] = "War Paint";
84
+ // Start checking for inputs
85
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Recipe"] = 8] = "Recipe";
86
+ // We know what we should look for in Killstreak Kits
87
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Professional Killstreak Kit"] = 4] = "Professional Killstreak Kit";
88
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Specialized Killstreak Kit"] = 5] = "Specialized Killstreak Kit";
89
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Killstreak Kit"] = 6] = "Killstreak Kit";
90
+ // Start by checking for parts on any weapons
91
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Primary weapon"] = 1] = "Primary weapon";
92
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Secondary weapon"] = 1] = "Secondary weapon";
93
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Melee weapon"] = 1] = "Melee weapon";
94
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Building"] = 1] = "Building";
95
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Primary PDA"] = 1] = "Primary PDA";
96
+ TypeToIdentifiableDescription[TypeToIdentifiableDescription["Secondary PDA"] = 1] = "Secondary PDA";
97
+ })(TypeToIdentifiableDescription || (TypeToIdentifiableDescription = {}));
98
+ const WEAPON_TYPES = new Set([
99
+ 'Primary weapon',
100
+ 'Secondary weapon',
101
+ 'Melee weapon',
102
+ 'Building',
103
+ 'Primary PDA',
104
+ 'Secondary PDA',
105
+ ]);
106
+ class EconParser extends parser_1.Parser {
107
+ extract(item) {
108
+ var _a, _b, _c;
109
+ const tags = EconParser.getTagAttributes(item);
110
+ let start;
111
+ if (tags.Type !== undefined) {
112
+ start = TypeToIdentifiableDescription[tags.Type];
113
+ }
114
+ else if (item.type !== '') {
115
+ // If we are missing the type from the tags we can in some cases get it
116
+ // from the type attribute of the item.
117
+ const match = item.type.match(/Level\s\d+\s(.+)/);
118
+ if (match !== null) {
119
+ start = TypeToIdentifiableDescription[match[1]];
120
+ }
121
+ }
122
+ const descriptions = EconParser.getDescriptionAttributes(item, tags, start);
123
+ const raw = {
124
+ type: (_a = tags.Type) !== null && _a !== void 0 ? _a : null,
125
+ assetid: item.assetid,
126
+ defindex: EconParser.getDefindex(item),
127
+ quality: (_b = tags.Quality) !== null && _b !== void 0 ? _b : null,
128
+ elevated: false,
129
+ craftable: descriptions.craftable,
130
+ tradable: item.tradable,
131
+ australium: EconParser.isAustralium(item, tags),
132
+ festivized: descriptions.festivized,
133
+ effect: descriptions.effect,
134
+ wear: (_c = tags.Exterior) !== null && _c !== void 0 ? _c : null,
135
+ paint: descriptions.paint,
136
+ killstreak: EconParser.getKillstreak(descriptions),
137
+ sheen: descriptions.sheen,
138
+ killstreaker: descriptions.killstreaker,
139
+ spells: descriptions.spells,
140
+ parts: descriptions.parts,
141
+ paintkit: descriptions.paintkit,
142
+ uses: descriptions.uses,
143
+ inputs: descriptions.inputs,
144
+ output: descriptions.output,
145
+ outputQuality: null,
146
+ target: descriptions.target,
147
+ crateSeries: EconParser.getCrateSeries(tags, item),
148
+ };
149
+ // Clean up the painkit name and overwrite the quality in certain cases
150
+ if (raw.paintkit !== null) {
151
+ if (tags.Rarity !== undefined && tags.Exterior !== undefined) {
152
+ // The length is the length of the description minus the length of the
153
+ // grade and wear
154
+ const length = item.descriptions[0].value.length -
155
+ (tags.Exterior.length + 3 + tags.Rarity.length + 7);
156
+ // The new paintkit is the old paintkit without the name of the item
157
+ raw.paintkit = raw.paintkit.slice(0, raw.paintkit.length - length - 1);
158
+ }
159
+ else {
160
+ // Some items with a paintkit does not have a rarity, so we remove
161
+ // the " War Paint" from the paintkit name manually
162
+ const index = raw.paintkit.indexOf(' War Paint');
163
+ if (index !== -1) {
164
+ raw.paintkit = raw.paintkit.slice(0, index);
165
+ }
166
+ }
167
+ const isWarPaint = tags.Type === 'War Paint';
168
+ // Set the quality of the item based on certain conditions to better match
169
+ // how the TF2 GC stores the quality of items with a paint kit.
170
+ if (raw.effect !== null && !isWarPaint) {
171
+ // The quality of unusual war paints is still "Unusual", hence the check
172
+ // for the type.
173
+ raw.quality = 'Decorated Weapon';
174
+ }
175
+ else if (isWarPaint && descriptions.statclock) {
176
+ // If the item is a war paint and it has a statclock attatched then we
177
+ // know that the quality is strange.
178
+ raw.quality = 'Strange';
179
+ }
180
+ }
181
+ // Handle elevated qualities
182
+ if (raw.quality !== 'Strange') {
183
+ const lastChar = item.type.charCodeAt(item.type.length - 1);
184
+ // Check if last char is a number
185
+ if (lastChar >= 48 && lastChar <= 57) {
186
+ let dashIndex = -1;
187
+ let colonIndex = -1;
188
+ // We can skip 2 chars, this is because we know that there is atleast a
189
+ // space before the number
190
+ for (let i = item.type.length - 3; i >= 0; i--) {
191
+ if (item.type.charAt(i) === '-') {
192
+ dashIndex = i;
193
+ break;
194
+ }
195
+ if (item.type.charAt(i) === ':') {
196
+ colonIndex = i;
197
+ }
198
+ }
199
+ if (colonIndex > dashIndex) {
200
+ raw.elevated = true;
201
+ }
202
+ }
203
+ }
204
+ if (raw.output !== null) {
205
+ // This is a little dumb but it is for consistency with the TF2 API
206
+ raw.outputQuality = 'Unique';
207
+ if (raw.killstreak !== 0) {
208
+ // Handle Killstreak Kit Fabricators
209
+ raw.target = raw.output.slice(raw.output.indexOf('Killstreak ') + 11, raw.output.lastIndexOf(' Kit'));
210
+ raw.output = 'Kit';
211
+ }
212
+ else if (raw.output.endsWith('Strangifier')) {
213
+ // Handle Strangifier Chemistry Sets
214
+ raw.target = raw.output.slice(0, raw.output.length - 12);
215
+ raw.output = 'Strangifier';
216
+ }
217
+ else if (raw.output.startsWith("Collector's ")) {
218
+ // Handle Collector's Chemistry Sets
219
+ raw.output = raw.output.slice(12);
220
+ raw.outputQuality = "Collector's";
221
+ }
222
+ }
223
+ return raw;
224
+ }
225
+ parse(raw) {
226
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
227
+ if (raw.defindex === null) {
228
+ throw new Error('Defindex is null');
229
+ }
230
+ // TODO: Can probably optimize the fetching of values
231
+ // Maybe start fetching and then await the promises at the end?
232
+ // Get quality from some cache
233
+ let quality = raw.quality !== null ? this.schema.getQualityByName(raw.quality) : null;
234
+ if (quality === undefined) {
235
+ // Quality is not in cache, fetch it from the schema
236
+ quality = yield this.schema.fetchQualityByName(raw.quality);
237
+ }
238
+ else if (quality instanceof Error) {
239
+ throw quality;
240
+ }
241
+ if (!quality) {
242
+ throw new Error('Quality is undefined');
243
+ }
244
+ let effect = raw.effect !== null ? this.schema.getEffectByName(raw.effect) : null;
245
+ if (effect === undefined) {
246
+ effect = yield this.schema.fetchEffectByName(raw.effect);
247
+ }
248
+ else if (effect instanceof Error) {
249
+ throw effect;
250
+ }
251
+ let paintkit = raw.paintkit !== null ? this.schema.getTextureByName(raw.paintkit) : null;
252
+ if (paintkit === undefined) {
253
+ paintkit = yield this.schema.fetchTextureByName(raw.paintkit);
254
+ }
255
+ else if (paintkit instanceof Error) {
256
+ throw paintkit;
257
+ }
258
+ let wear = null;
259
+ if (raw.wear !== null) {
260
+ wear = WEAR[raw.wear];
261
+ }
262
+ let target = raw.target !== null ? this.schema.getDefindexByName(raw.target) : null;
263
+ if (target === undefined) {
264
+ target = yield this.schema.fetchDefindexByName(raw.target);
265
+ }
266
+ let outputQuality = raw.outputQuality !== null
267
+ ? this.schema.getQualityByName(raw.outputQuality)
268
+ : null;
269
+ if (outputQuality === undefined) {
270
+ outputQuality = yield this.schema.fetchQualityByName(raw.outputQuality);
271
+ }
272
+ else if (outputQuality instanceof Error) {
273
+ throw outputQuality;
274
+ }
275
+ let output = null;
276
+ if (raw.output !== null) {
277
+ if (raw.output === 'Kit') {
278
+ output = KILLSTREAK_FABRICATORS[raw.killstreak];
279
+ }
280
+ else if (raw.output === 'Strangifier') {
281
+ output = 6522;
282
+ }
283
+ else if (raw.defindex === 20006) {
284
+ output = yield this.schema.fetchDefindexByName(raw.output);
285
+ }
286
+ else {
287
+ const intermediate = this.schema.getDefindexByName(raw.output);
288
+ if (intermediate === undefined) {
289
+ output = yield this.schema.fetchDefindexByName(raw.output);
290
+ }
291
+ else {
292
+ output = intermediate;
293
+ }
294
+ }
295
+ }
296
+ const parts = [];
297
+ for (const scoreType of raw.parts) {
298
+ const part = yield this.getPartByScoreType(scoreType, raw.type);
299
+ if (part !== null) {
300
+ parts.push(part);
301
+ }
302
+ }
303
+ const spells = [];
304
+ for (const spell of raw.spells) {
305
+ const cached = this.schema.getSpellByName(spell);
306
+ if (cached instanceof Error) {
307
+ throw cached;
308
+ }
309
+ else if (cached !== undefined) {
310
+ spells.push(cached);
311
+ continue;
312
+ }
313
+ const fetched = yield this.schema.fetchSpellByName(spell);
314
+ if (fetched) {
315
+ spells.push(fetched);
316
+ }
317
+ }
318
+ let paint = raw.paint !== null ? this.schema.getDefindexByName(raw.paint) : null;
319
+ if (paint === undefined) {
320
+ paint = yield this.schema.fetchDefindexByName(raw.paint);
321
+ }
322
+ let inputs = null;
323
+ if (raw.inputs !== null) {
324
+ inputs = [];
325
+ for (const rawInput of raw.inputs) {
326
+ let name = rawInput.name;
327
+ let rawQuality = 'Unique';
328
+ // Handle strange items for strangifier chemistry sets
329
+ if (name.startsWith('Strange ') && raw.output === 'Strangifier') {
330
+ name = name.slice(8);
331
+ rawQuality = 'Strange';
332
+ }
333
+ // Get the quality
334
+ let quality = this.schema.getQualityByName(rawQuality);
335
+ if (quality === undefined) {
336
+ quality = yield this.schema.fetchQualityByName(rawQuality);
337
+ }
338
+ else if (quality instanceof Error) {
339
+ throw quality;
340
+ }
341
+ const input = {
342
+ quality,
343
+ amount: rawInput.amount,
344
+ };
345
+ // Handle killstreak items for killstreak kit fabricators
346
+ if (name.startsWith('Unique') && name.endsWith(' Killstreak Item')) {
347
+ const killstreak = name.slice(7, name.length - 5);
348
+ name = null;
349
+ input.killstreak = KILLSTREAK_TIERS[killstreak];
350
+ }
351
+ if (name !== null) {
352
+ // Get the defindex of the input item
353
+ let defindex = this.getInputByName(name);
354
+ if (defindex === undefined) {
355
+ defindex = yield this.fetchInputByName(name);
356
+ }
357
+ if (!defindex) {
358
+ throw new Error(`Could not find the defindex of the input item "${name}"`);
359
+ }
360
+ input.defindex = defindex;
361
+ }
362
+ inputs.push(input);
363
+ }
364
+ }
365
+ const parsed = {
366
+ assetid: raw.assetid,
367
+ defindex: raw.defindex,
368
+ quality,
369
+ craftable: raw.craftable,
370
+ tradable: raw.tradable,
371
+ australium: raw.australium,
372
+ festivized: raw.festivized,
373
+ effect,
374
+ wear,
375
+ paintkit,
376
+ killstreak: raw.killstreak,
377
+ target,
378
+ output,
379
+ outputQuality,
380
+ elevated: raw.elevated,
381
+ crateSeries: raw.crateSeries,
382
+ paint,
383
+ parts,
384
+ spells,
385
+ sheen: raw.sheen,
386
+ killstreaker: raw.killstreaker,
387
+ inputs,
388
+ };
389
+ return parsed;
390
+ });
391
+ }
392
+ getPartByScoreType(scoreType, itemType) {
393
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
394
+ let part = this.schema.getStrangePartByScoreType(scoreType);
395
+ if (part === undefined) {
396
+ part = yield this.schema.fetchStrangePartByScoreType(scoreType);
397
+ }
398
+ if (!part) {
399
+ return part;
400
+ }
401
+ let item = this.schema.getItemByDefindex(part);
402
+ if (item === undefined) {
403
+ item = yield this.schema.fetchItemByDefindex(part);
404
+ }
405
+ else if (item instanceof Error) {
406
+ throw item;
407
+ }
408
+ if (!item) {
409
+ return null;
410
+ }
411
+ if (item.name.startsWith('Strange Cosmetic Part: ') &&
412
+ itemType !== 'Cosmetic') {
413
+ return null;
414
+ }
415
+ return part;
416
+ });
417
+ }
418
+ getInputByName(name) {
419
+ if (name.startsWith('The ')) {
420
+ const input = this.schema.getDefindexByName(name.slice(4));
421
+ if (input) {
422
+ return input;
423
+ }
424
+ }
425
+ return this.schema.getDefindexByName(name);
426
+ }
427
+ fetchInputByName(name) {
428
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
429
+ if (name.startsWith('The ')) {
430
+ const input = yield this.schema.fetchDefindexByName(name.slice(4));
431
+ if (input) {
432
+ return input;
433
+ }
434
+ }
435
+ return this.schema.fetchDefindexByName(name);
436
+ });
437
+ }
438
+ static getDefindex(item) {
439
+ for (const action of item.actions) {
440
+ if (action.name !== 'Item Wiki Page...') {
441
+ continue;
442
+ }
443
+ const defindex = new URL(action.link).searchParams.get('id');
444
+ if (defindex === null) {
445
+ return null;
446
+ }
447
+ return parseInt(defindex, 10);
448
+ }
449
+ return null;
450
+ }
451
+ static getKillstreak(descriptions) {
452
+ if (descriptions.killstreaker !== null) {
453
+ return 3;
454
+ }
455
+ else if (descriptions.sheen !== null) {
456
+ return 2;
457
+ }
458
+ else if (descriptions.killstreak) {
459
+ return 1;
460
+ }
461
+ else {
462
+ return 0;
463
+ }
464
+ }
465
+ static getCrateSeries(tags, item) {
466
+ if (tags.Type !== 'Crate') {
467
+ return null;
468
+ }
469
+ // We can get the crate series from the description of the item, but that
470
+ // would add more description checks.
471
+ const lastChar = item.market_hash_name.charCodeAt(item.market_hash_name.length - 1);
472
+ if (!(lastChar >= 48 && lastChar <= 57)) {
473
+ return null;
474
+ }
475
+ // We know there is atleast one number in the name
476
+ let series = lastChar - 48;
477
+ const length = item.market_hash_name.length;
478
+ // We start from the end of the string and go backwards towards a number sign
479
+ for (let i = length - 2; i >= length - 4; i--) {
480
+ // Get the char code of the current character
481
+ const charCode = item.market_hash_name.charCodeAt(i);
482
+ if (charCode >= 48 && charCode <= 57) {
483
+ // The character is a number
484
+ series = series + (charCode - 48) * Math.pow(10, length - i - 1);
485
+ }
486
+ else if (charCode === 35) {
487
+ // The character is a number sign
488
+ return series;
489
+ }
490
+ else {
491
+ // The character is not a number or a number sign
492
+ break;
493
+ }
494
+ }
495
+ return null;
496
+ }
497
+ static getTagAttributes(item) {
498
+ const tags = {};
499
+ let tagCount = 0;
500
+ for (const tag of item.tags) {
501
+ if (TAGS_OF_INTEREST.has(tag.category)) {
502
+ tagCount++;
503
+ tags[tag.category] = tag.name;
504
+ if (tagCount === TAGS_OF_INTEREST.size) {
505
+ break;
506
+ }
507
+ }
508
+ }
509
+ return tags;
510
+ }
511
+ static isAustralium(item, tags) {
512
+ var _a;
513
+ // It might be possible for australiums to be qualities other than Strange.
514
+ // But I think all australium items are a weapon of some sort.
515
+ if (!WEAPON_TYPES.has((_a = tags.Type) !== null && _a !== void 0 ? _a : '')) {
516
+ return false;
517
+ }
518
+ // All australium items have Australium in their name. Except for "Saxxy",
519
+ // but that item is always australium.
520
+ return item.market_hash_name.includes('Australium ');
521
+ }
522
+ /**
523
+ * This function tries to extract as much useful information as possible from
524
+ * the descriptions of an item.
525
+ * @param item The item to extract the information from
526
+ * @param tags The tags of the item
527
+ * @param start The description to start from
528
+ * @returns An object with the extracted information
529
+ */
530
+ static getDescriptionAttributes(item, tags, start = IdentifiableDescription.Paint) {
531
+ const attributes = {
532
+ craftable: true,
533
+ effect: null,
534
+ killstreak: false,
535
+ sheen: null,
536
+ killstreaker: null,
537
+ spells: [],
538
+ parts: [],
539
+ paint: null,
540
+ festivized: false,
541
+ paintkit: null,
542
+ uses: null,
543
+ inputs: null,
544
+ output: null,
545
+ target: null,
546
+ grade: null,
547
+ statclock: false,
548
+ };
549
+ if (start === IdentifiableDescription.Skip) {
550
+ return attributes;
551
+ }
552
+ let next = start;
553
+ const descriptions = item.descriptions;
554
+ // Add empty description to make sure we do not get out of bounds errors
555
+ descriptions.push({ value: '' });
556
+ let i = 0;
557
+ // I don't like that this is outside the switch statement but it is the most
558
+ // efficient way to do it.
559
+ if (tags.Rarity !== undefined) {
560
+ if (tags.Exterior !== undefined &&
561
+ descriptions[i].value.startsWith(tags.Rarity + ' Grade ') &&
562
+ descriptions[i].value.endsWith(' (' + tags.Exterior + ')')) {
563
+ attributes.grade = descriptions[i].value;
564
+ i++;
565
+ }
566
+ if (descriptions[i].value === 'Strange Stat Clock Attached') {
567
+ attributes.statclock = true;
568
+ i++;
569
+ }
570
+ }
571
+ loop: while (i < descriptions.length - 1) {
572
+ // Skip descriptions that are too short/long to contain any useful information
573
+ // TODO: Fix "magic numbers"
574
+ if (descriptions[i].value.length < 10) {
575
+ i++;
576
+ continue;
577
+ }
578
+ /* eslint-disable no-fallthrough */
579
+ switch (next) {
580
+ case IdentifiableDescription.All:
581
+ case IdentifiableDescription.Paint:
582
+ if (descriptions[i].value.startsWith('Paint Color: ')) {
583
+ attributes.paint = descriptions[i].value.slice(13);
584
+ next = IdentifiableDescription.Spells;
585
+ i++;
586
+ }
587
+ case IdentifiableDescription.Spells:
588
+ if (descriptions[i].value.startsWith('Halloween: ')) {
589
+ attributes.spells.push(descriptions[i].value.slice(11, descriptions[i].value.lastIndexOf(' (spell only active during event)')));
590
+ // This is redundant
591
+ next = IdentifiableDescription.Spells;
592
+ i++;
593
+ // We break because that might be the best to do if there are more spells
594
+ continue loop;
595
+ }
596
+ case IdentifiableDescription.Parts:
597
+ if (descriptions[i].value.startsWith('(') &&
598
+ // Make sure it does not match Craftable/Tradable/Marketable descriptions
599
+ descriptions[i].value.charAt(1) !== ' ') {
600
+ attributes.parts.push(descriptions[i].value.slice(1, descriptions[i].value.lastIndexOf(': ')));
601
+ next = IdentifiableDescription.Parts;
602
+ i++;
603
+ // Break again so we can check for more parts
604
+ continue loop;
605
+ }
606
+ else if (descriptions[i].value.startsWith(' ')) {
607
+ attributes.parts.push(descriptions[i].value.slice(5, descriptions[i].value.lastIndexOf(': ')));
608
+ next = IdentifiableDescription.Parts;
609
+ i++;
610
+ // Break again so we can check for more parts
611
+ continue loop;
612
+ }
613
+ case IdentifiableDescription.Effect:
614
+ if (descriptions[i].value.startsWith('\u2605 Unusual Effect: ')) {
615
+ attributes.effect = descriptions[i].value.slice(18);
616
+ next = IdentifiableDescription.Festivized;
617
+ i++;
618
+ }
619
+ case IdentifiableDescription.Festivized:
620
+ if (descriptions[i].value === 'Festivized') {
621
+ attributes.festivized = true;
622
+ next = IdentifiableDescription.Killstreaker;
623
+ i++;
624
+ }
625
+ case IdentifiableDescription.Killstreaker:
626
+ if (descriptions[i].value.startsWith('Killstreaker: ')) {
627
+ attributes.killstreaker = descriptions[i].value.slice(14);
628
+ next = IdentifiableDescription.Sheen;
629
+ i++;
630
+ }
631
+ case IdentifiableDescription.Sheen:
632
+ if (descriptions[i].value.startsWith('Sheen: ')) {
633
+ attributes.sheen = descriptions[i].value.slice(7);
634
+ next = IdentifiableDescription.Killstreak;
635
+ i++;
636
+ }
637
+ case IdentifiableDescription.Killstreak:
638
+ if (descriptions[i].value === 'Killstreaks Active') {
639
+ attributes.killstreak = true;
640
+ next = IdentifiableDescription.Texture;
641
+ i++;
642
+ }
643
+ case IdentifiableDescription.Texture: {
644
+ let extract = false;
645
+ if (descriptions[i].value.endsWith('Collection')) {
646
+ if (tags.Exterior !== undefined) {
647
+ next = IdentifiableDescription.Texture;
648
+ extract = true;
649
+ }
650
+ i++;
651
+ // Might be a little "dangerous" to use these while loops
652
+ while (descriptions[i].value.charAt(0) === ' ') {
653
+ i++;
654
+ }
655
+ }
656
+ let paintkit = null;
657
+ let found = false;
658
+ if (descriptions[i].value.startsWith('\u2714 ')) {
659
+ paintkit = descriptions[i].value.slice(2);
660
+ found = true;
661
+ }
662
+ else if (descriptions[i].value.startsWith('\u2605 ')) {
663
+ found = true;
664
+ paintkit = descriptions[i].value.slice(2);
665
+ }
666
+ if (found) {
667
+ i++;
668
+ // Skip all the other stuff if any
669
+ while (descriptions[i].value.charAt(0) === ' ') {
670
+ i++;
671
+ }
672
+ if (extract) {
673
+ attributes.paintkit = paintkit;
674
+ next = IdentifiableDescription.Uses;
675
+ }
676
+ // We continue the loop so that we skip to the next checks
677
+ continue loop;
678
+ }
679
+ }
680
+ case IdentifiableDescription.Input:
681
+ if (descriptions[i].value ===
682
+ 'The following are the inputs that must be fulfilled.') {
683
+ i++;
684
+ const inputs = [];
685
+ attributes.inputs = inputs;
686
+ while (descriptions[i].value !== ' ') {
687
+ const split = descriptions[i].value.lastIndexOf(' x ');
688
+ const name = descriptions[i].value.slice(0, split);
689
+ const amount = parseInt(descriptions[i].value.slice(split + 3), 10);
690
+ inputs.push({ name, amount });
691
+ i++;
692
+ }
693
+ next = IdentifiableDescription.Output;
694
+ }
695
+ case IdentifiableDescription.Output:
696
+ if (descriptions[i].value ===
697
+ 'You will receive all of the following outputs once all of the inputs are fulfilled.') {
698
+ i++;
699
+ attributes.output = descriptions[i].value;
700
+ i++;
701
+ if (descriptions[i].value !== ' ') {
702
+ let start = 1;
703
+ if (descriptions[i].value.startsWith('(Killstreaker: ')) {
704
+ start = descriptions[i].value.indexOf(', ');
705
+ attributes.killstreaker = descriptions[i].value.slice(15, start);
706
+ start += 2;
707
+ }
708
+ attributes.sheen = descriptions[i].value.slice(start + 7, descriptions[i].value.length - 1);
709
+ i++;
710
+ }
711
+ next = IdentifiableDescription.Uses;
712
+ }
713
+ case IdentifiableDescription.Target:
714
+ if (descriptions[i].value.startsWith('This ')) {
715
+ // Can probably speed this up by just slicing strings instead of
716
+ // using regex
717
+ const match = descriptions[i].value.match(/^This .*? can be applied to a (.*?)(?=\.)/);
718
+ if (match !== null) {
719
+ attributes.target = match[1];
720
+ next = IdentifiableDescription.Uses;
721
+ i++;
722
+ }
723
+ }
724
+ case IdentifiableDescription.Uses:
725
+ if (descriptions[i].value.startsWith('This is a limited use item. Uses: ')) {
726
+ attributes.uses = parseInt(descriptions[i].value.slice(34), 10);
727
+ next = IdentifiableDescription.Craftable;
728
+ i++;
729
+ }
730
+ case IdentifiableDescription.Craftable:
731
+ if (NON_CRAFTABLE_DESCRIPTIONS[descriptions[i].value]) {
732
+ attributes.craftable = false;
733
+ // We found everything we could possibly look for.
734
+ break loop;
735
+ }
736
+ default:
737
+ i++;
738
+ continue loop;
739
+ }
740
+ /* eslint-enable no-fallthrough */
741
+ }
742
+ return attributes;
743
+ }
744
+ }
745
+ exports.EconParser = EconParser;
746
+ tslib_1.__exportStar(require("./types"), exports);
747
+ //# sourceMappingURL=parser.js.map