Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Internal backpack #1602

Open
wants to merge 25 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b191297
🧪 read the tf2.backpack content
idinium96 Nov 27, 2022
6ee51ed
🔨 forgot
idinium96 Nov 27, 2022
7582433
🔨 no need decimals
idinium96 Nov 27, 2022
8d66558
Merge branch 'development' into internal-backpack
idinium96 Jan 3, 2023
53e2c25
🧪 test with tf2-backpack
idinium96 Jan 3, 2023
3ae0564
🔄️ temp
idinium96 Jan 3, 2023
e99d846
🔎 itemSchemaLoaded never emitted, checking...
idinium96 Jan 3, 2023
511bf04
🔨 I see
idinium96 Jan 3, 2023
21deba6
Merge branch 'development' into internal-backpack
idinium96 Jan 4, 2023
48e7fb7
🔨🧪 use different approach
idinium96 Jan 4, 2023
b66abfb
🔨 fix "Unknown job type" error
idinium96 Jan 4, 2023
8c5157e
🚮 lel
idinium96 Jan 4, 2023
78a2332
🔄️ change log position
idinium96 Jan 4, 2023
3833b29
🧪 waitForBackpackLoaded
idinium96 Jan 4, 2023
9527924
🔨 fix job.callback is not a function
idinium96 Jan 4, 2023
5af62c9
🔨 not needed
idinium96 Jan 4, 2023
54fddab
Merge branch 'development' into internal-backpack
idinium96 Jan 4, 2023
c446d57
🔨 remove save backpack, refactor code.
idinium96 Jan 4, 2023
436eebd
📋 add TODO
idinium96 Jan 4, 2023
2fc88aa
Merge branch 'development' into internal-backpack
idinium96 Jan 17, 2023
b7eb67e
Merge branch 'development' into internal-backpack
idinium96 Jul 17, 2023
6a90877
Merge branch 'development' into internal-backpack
idinium96 Jul 19, 2023
27f946e
Merge branch 'development' into internal-backpack
idinium96 Jul 19, 2023
a8c5d63
✨ add new static methods (WIP)
idinium96 Aug 1, 2023
ad503bf
Merge branch 'development' into internal-backpack
idinium96 Aug 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"steam-user": "^4.28.6",
"steamid": "^2.0.0",
"url": "^0.11.1",
"tf2-backpack": "^1.1.5",
"valid-url": "^1.0.9",
"winston": "^3.10.0",
"winston-daily-rotate-file": "^4.7.1",
Expand Down
27 changes: 16 additions & 11 deletions src/classes/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import pluralize from 'pluralize';
import * as timersPromises from 'timers/promises';
import fs from 'fs';
import path from 'path';
import { BackpackParser } from 'tf2-backpack';
import * as files from '../lib/files';

// Reference: https://github.com/tf2-automatic/tf2-automatic/commit/cf7b807cae11eb172a78ef184bbafdb4ebe86501#diff-58f39591209025b16105c9f25a34c119332983a0d8cea7819b534d9d408324c4L329
Expand Down Expand Up @@ -87,6 +88,10 @@ export default class Bot {

readonly inventoryGetter: InventoryGetter;

backpackParser: BackpackParser;

needSave = false;

readonly boundInventoryGetter: (
steamID: SteamID | string,
appid: number,
Expand Down Expand Up @@ -123,8 +128,6 @@ export default class Bot {
spy: string[];
};

public updateSchemaPropertiesInterval: NodeJS.Timeout;

// Settings
private readonly maxLoginAttemptsWithinPeriod: number = 3;

Expand Down Expand Up @@ -995,6 +998,17 @@ export default class Bot {
log.info('Getting TF2 schema...');
void this.initializeSchema().asCallback(callback);
},
(callback): void => {
log.info('Initializing Backpack parser...');
this.setProperties();
this.backpackParser = new BackpackParser(this.schema.raw.items_game);
this.schemaManager.on('schema', () => {
this.backpackParser = new BackpackParser(this.schema.raw.items_game);
this.setProperties();
});
/* eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call */
return callback(null);
},
(callback: (err?) => void): void => {
log.info('Initializing pricelist...');

Expand Down Expand Up @@ -1230,15 +1244,6 @@ export default class Bot {
sniper: this.schema.getWeaponsForCraftingByClass('Sniper'),
spy: this.schema.getWeaponsForCraftingByClass('Spy')
};

clearInterval(this.updateSchemaPropertiesInterval);
this.refreshSchemaProperties();
}

private refreshSchemaProperties(): void {
this.updateSchemaPropertiesInterval = setInterval(() => {
this.setProperties();
}, 24 * 60 * 60 * 1000);
}

setCookies(cookies: string[]): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/classes/BotManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ export default class BotManager {
// Stop updating schema
clearTimeout(this.schemaManager?._updateTimeout);
clearInterval(this.schemaManager?._updateInterval);
clearInterval(this.bot.updateSchemaPropertiesInterval);

// Stop heartbeat and inventory timers
clearInterval(this.bot.listingManager?._heartbeatInterval);
Expand Down
78 changes: 74 additions & 4 deletions src/classes/Inventory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import SteamID from 'steamid';
import { EconItem, ItemAttributes, PartialSKUWithMention } from '@tf2autobot/tradeoffer-manager';
import { Effect, Paints, StrangeParts } from '@tf2autobot/tf2-schema';
import SchemaManager, { Item, Effect, Paints, StrangeParts } from '@tf2autobot/tf2-schema';
import SKU from '@tf2autobot/tf2-sku';
import { HighValue } from './Options';
import Bot from './Bot';
import { noiseMakers, spellsData, killstreakersData, sheensData } from '../lib/data';
import Pricelist from './Pricelist';
import * as bp from 'tf2-backpack';

export default class Inventory {
private readonly steamID: SteamID;
Expand Down Expand Up @@ -70,7 +71,7 @@ export default class Inventory {
) => void
): Inventory {
const inventory = new Inventory(steamID, bot, which, boundInventoryGetter);
inventory.setItems = items;
inventory.setItemsEcon({ items });
return inventory;
}

Expand Down Expand Up @@ -134,13 +135,13 @@ export default class Inventory {
return reject(err);
}

this.setItems = items;
this.setItemsEcon({ items });
resolve();
});
});
}

private set setItems(items: EconItem[]) {
private setItemsEcon({ items }: { items: EconItem[] }) {
this.tradable = Inventory.createDictionary(
items.filter(item => item.tradable),
this.bot,
Expand All @@ -155,6 +156,8 @@ export default class Inventory {
);
}

// TODO: Process internal inventory

findByAssetid(assetid: string): string | null {
for (const sku in this.tradable) {
if (!Object.prototype.hasOwnProperty.call(this.tradable, sku)) {
Expand Down Expand Up @@ -595,6 +598,73 @@ export default class Inventory {
return attributes;
}

// For tf2-backpack
private static getSKU({
item,
schema,
normalizeFestivizedItems,
normalizeStrangeAsSecondQuality,
normalizePainted,
normalizeCraftNumber,
paintsInOptions
}: {
item: bp.Item<number>;
schema: SchemaManager.Schema;
normalizeFestivizedItems: boolean;
normalizeStrangeAsSecondQuality: boolean;
normalizePainted: boolean;
normalizeCraftNumber: boolean;
paintsInOptions: string[];
}): { sku: string; isPainted: boolean } {
const paint = this.getPaint(schema, item, normalizePainted, paintsInOptions);

const itemData: Item = {
defindex: item.defindex,
quality: item.quality,
craftable: item.craftable,
killstreak: item.killstreakTier || null,
australium: item.australium || null,
festive: !normalizeFestivizedItems ? item.festivized : false,
effect: item.effect || null,
wear: item.wear || null,
paintkit: item.paintkit || null,
quality2: !normalizeStrangeAsSecondQuality ? (item.elevated ? 11 : null) : null,
crateseries: item.crateNo || null,
target: item.target || null,
output: item.outputItem?.defindex,
outputQuality: item.outputItem?.quality,
paint: paint.decimalValue,
craftnumber: !normalizeCraftNumber ? item.craft : null
};

return { sku: SKU.fromObject(itemData), isPainted: paint.isPainted };
}

// For tf2-backpack
private static getPaint(
schema: SchemaManager.Schema,
item: bp.Item<number>,
normalize: boolean,
inOptions: string[]
): { decimalValue: number; isPainted: boolean } {
if (!item.paint) {
return null;
}

if (!normalize) {
if (item.paint === 'B8383B' && item.paint_other !== '5885A2' && inOptions.includes('legacy paint')) {
// legacy paint for Team Spirit
return { decimalValue: 5801378, isPainted: true };
}

if (inOptions.includes(item.paint.toLowerCase())) {
return { decimalValue: schema.paints[bp.paints[item.paint]], isPainted: true };
}
}

return { decimalValue: null, isPainted: false };
}

clearFetch(): void {
this.tradable = undefined;
this.nonTradable = undefined;
Expand Down
26 changes: 25 additions & 1 deletion src/classes/TF2GC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ type Job = {
| 'delete'
| 'sort'
| 'removeAttributes'
| 'craftToken';
| 'craftToken'
| 'backpackLoad';
defindex?: number;
sku?: string;
skus?: string[];
Expand Down Expand Up @@ -151,6 +152,12 @@ export default class TF2GC {
this.newJob({ type: 'craftToken', assetids, tokenType, subTokenType, callback: callback });
}

waitForBackpackLoaded(callback: (err: Error | null) => void): void {
log.debug(`Enqueueing backpackLoaded trigger job`);

this.newJob({ type: 'backpackLoad', callback: callback });
}

private newJob(job: Job): void {
this.jobs.push(job);
this.handleJobQueue();
Expand Down Expand Up @@ -203,6 +210,8 @@ export default class TF2GC {
func = this.handleSortJob.bind(this, job);
} else if (job.type === 'craftToken') {
func = this.handleCraftTokenJob.bind(this, job);
} else if (job.type === 'backpackLoad') {
func = this.handleBackpackLoadedJob.bind(this, job);
}

if (func) {
Expand Down Expand Up @@ -527,6 +536,21 @@ export default class TF2GC {
);
}

private handleBackpackLoadedJob(): void {
this.bot.client.gamesPlayed([]);
this.bot.client.gamesPlayed(440);

this.listenForEvent(
'backpackLoaded',
() => {
this.finishedProcessingJob();
},
err => {
this.finishedProcessingJob(err);
}
);
}

/**
* Listens for GC event
*
Expand Down
84 changes: 37 additions & 47 deletions src/classes/Trades.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export default class Trades {

private resetRetryAcceptOfferTimeout: NodeJS.Timeout;

private retryFetchInventoryTimeout: NodeJS.Timeout;
private retryRefreshInventoryTimeout: NodeJS.Timeout;

private calledRetryFetchFreq = 0;
private calledRetryRefreshFreq = 0;

private offerChangedAcc: { offer: TradeOffer; oldState: number; timeTakenToComplete: number }[] = [];

Expand Down Expand Up @@ -1655,42 +1655,33 @@ export default class Trades {
// Just handle changes
this.bot.handler.onTradeOfferChanged(offer, oldState, timeTakenToComplete);
} else {
// Exit all running apps ("TF2Autobot" or custom, and Team Fortress 2)
// Will play again after craft/smelt/sort inventory job
// https://github.com/TF2Autobot/tf2autobot/issues/527
this.bot.client.gamesPlayed([]);

this.offerChangedAcc.push({ offer, oldState, timeTakenToComplete });
log.debug('Accumulated offerChanged: ', this.offerChangedAcc.length);

if (this.offerChangedAcc.length <= 1) {
// Only call `fetch` if accumulated offerChanged is less than or equal to 1
// Prevent never ending "The request is a duplicate and the action has already occurred in the past, ignored this time"

// Accepted, Invalid trade (possible) => new item assetid
log.debug('Fetching our inventory...');
return void this.bot.inventoryManager.getInventory
.fetch()
.then(() => {
this.onSuccessfulFetch();
})
.catch(err => {
log.warn('Error fetching inventory: ', err);
log.debug('Retrying to fetch inventory in 30 seconds...');
this.calledRetryFetchFreq++;

if (this.calledRetryFetchFreq === 1) {
log.debug('Refreshing our inventory...');
return void this.bot.tf2gc.waitForBackpackLoaded(err => {
if (err) {
log.warn('Error running "waitForBackpackLoaded": ', err);
log.debug('Retrying to in 30 seconds...');
this.calledRetryRefreshFreq++;

if (this.calledRetryRefreshFreq === 1) {
// Only call this once (before reset)
this.retryFetchInventory();
this.retryRefreshInventory();
}
});
return;
}

this.onSuccessfulRefresh();
});
}

log.debug('Not fetching inventory this time...');
log.debug('Not refreshing inventory this time...');
}
}

private onSuccessfulFetch(): void {
private onSuccessfulRefresh(): void {
if (this.offerChangedAcc.length > 0) {
this.offerChangedAcc.forEach(el => {
this.bot.handler.onTradeOfferChanged(el.offer, el.oldState, el.timeTakenToComplete);
Expand All @@ -1701,34 +1692,33 @@ export default class Trades {
}
}

private retryFetchInventory(): void {
clearTimeout(this.retryFetchInventoryTimeout);
this.retryFetchInventoryTimeout = setTimeout(() => {
this.bot.inventoryManager.getInventory
.fetch()
.then(() => {
this.onSuccessfulFetch();

// Reset to 0
this.calledRetryFetchFreq = 0;
})
.catch(err => {
log.warn('Error fetching inventory: ', err);
private retryRefreshInventory(): void {
clearTimeout(this.retryRefreshInventoryTimeout);
this.retryRefreshInventoryTimeout = setTimeout(() => {
this.bot.tf2gc.waitForBackpackLoaded(err => {
if (err) {
log.warn('Error refreshing inventory: ', err);

if (this.calledRetryFetchFreq > 3) {
if (this.calledRetryRefreshFreq > 3) {
// If more than 3 times failed, then just proceed with an outdated inventory
this.onSuccessfulFetch();
this.onSuccessfulRefresh();

// Reset to 0
this.calledRetryFetchFreq = 0;
this.calledRetryRefreshFreq = 0;

return;
}

log.debug('Retrying to fetch inventory in 30 seconds...');
this.calledRetryFetchFreq++;
this.retryFetchInventory();
});
log.debug('Retrying to refresh inventory in 30 seconds...');
this.calledRetryRefreshFreq++;
return this.retryRefreshInventory();
}

this.onSuccessfulRefresh();

// Reset to 0
this.calledRetryRefreshFreq = 0;
});
}, 30 * 1000);
}

Expand Down