Big post-PR cleanup

This commit is contained in:
Archi 2024-03-17 02:29:04 +01:00
parent f98a159799
commit c9acbb7bf2
No known key found for this signature in database
GPG key ID: 6B138B4C64555AEA
29 changed files with 872 additions and 660 deletions

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -43,7 +43,7 @@ using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
internal static class Backend {
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
ArgumentNullException.ThrowIfNull(webBrowser);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
@ -74,7 +74,7 @@ internal static class Backend {
return await webBrowser.UrlPostToJsonObject<GenericResponse<BackgroundTaskResponse>, AnnouncementDiffRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
}
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
ArgumentNullException.ThrowIfNull(webBrowser);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
@ -131,7 +131,7 @@ internal static class Backend {
return response?.StatusCode;
}
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> acceptedMatchableTypes) {
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(webBrowser);
@ -161,7 +161,7 @@ internal static class Backend {
return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet<ListedUser>.Empty);
}
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
ArgumentNullException.ThrowIfNull(webBrowser);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,7 +40,7 @@ internal sealed class AnnouncementDiffRequest : AnnouncementRequest {
[JsonRequired]
private string PreviousInventoryChecksum { get; init; }
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -50,7 +50,7 @@ internal class AnnouncementRequest {
[JsonInclude]
[JsonRequired]
private ImmutableHashSet<Asset.EType> MatchableTypes { get; init; }
private ImmutableHashSet<EAssetType> MatchableTypes { get; init; }
[JsonInclude]
[JsonRequired]
@ -75,7 +75,7 @@ internal class AnnouncementRequest {
[JsonRequired]
private string TradeToken { get; init; }
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -41,7 +41,7 @@ internal class AssetForMatching {
[JsonInclude]
[JsonPropertyName("r")]
[JsonRequired]
internal Asset.ERarity Rarity { get; private init; }
internal EAssetRarity Rarity { get; private init; }
[JsonInclude]
[JsonPropertyName("e")]
@ -56,7 +56,7 @@ internal class AssetForMatching {
[JsonInclude]
[JsonPropertyName("p")]
[JsonRequired]
internal Asset.EType Type { get; private init; }
internal EAssetType Type { get; private init; }
[JsonConstructor]
protected AssetForMatching() { }

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -42,5 +42,5 @@ internal class AssetInInventory : AssetForMatching {
AssetID = asset.AssetID;
}
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription { ProtobufBody = { tradable = Tradable } }, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription(Asset.SteamAppID, ClassID, tradable: false, realAppID: RealAppID, type: Type, rarity: Rarity), AssetID);
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -42,13 +42,13 @@ internal sealed class InventoriesRequest {
[JsonInclude]
[JsonRequired]
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; }
[JsonInclude]
[JsonRequired]
internal ulong SteamID { get; private init; }
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> matchableTypes) {
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -37,7 +37,7 @@ internal sealed class ListedUser {
[JsonInclude]
[JsonRequired]
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = ImmutableHashSet<Asset.EType>.Empty;
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; } = ImmutableHashSet<EAssetType>.Empty;
[JsonInclude]
[JsonRequired]

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -38,7 +38,7 @@ internal sealed class SetPart {
[JsonInclude]
[JsonPropertyName("r")]
[JsonRequired]
internal Asset.ERarity Rarity { get; private init; }
internal EAssetRarity Rarity { get; private init; }
[JsonInclude]
[JsonPropertyName("e")]
@ -48,7 +48,7 @@ internal sealed class SetPart {
[JsonInclude]
[JsonPropertyName("p")]
[JsonRequired]
internal Asset.EType Type { get; private init; }
internal EAssetType Type { get; private init; }
[JsonConstructor]
private SetPart() { }

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -37,7 +37,7 @@ internal sealed class SetPartsRequest {
[JsonInclude]
[JsonRequired]
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; }
[JsonInclude]
[JsonRequired]
@ -47,7 +47,7 @@ internal sealed class SetPartsRequest {
[JsonRequired]
internal ulong SteamID { get; private init; }
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -63,11 +63,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
private const byte MinimumSteamGuardEnabledDays = 15; // As imposed by Steam limits
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
private static readonly FrozenSet<Asset.EType> AcceptedMatchableTypes = new HashSet<Asset.EType>(4) {
Asset.EType.Emoticon,
Asset.EType.FoilTradingCard,
Asset.EType.ProfileBackground,
Asset.EType.TradingCard
private static readonly FrozenSet<EAssetType> AcceptedMatchableTypes = new HashSet<EAssetType>(4) {
EAssetType.Emoticon,
EAssetType.FoilTradingCard,
EAssetType.ProfileBackground,
EAssetType.TradingCard
}.ToFrozenSet();
private readonly Bot Bot;
@ -231,7 +231,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
return;
}
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
@ -284,10 +284,10 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
List<AssetForListing> assetsForListing = [];
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), bool> tradableSets = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), bool> tradableSets = new();
foreach (Asset item in inventory) {
if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) {
if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) {
// Only tradable assets matter for MatchEverything bots
if (!matchEverything || item.Tradable) {
assetsForListing.Add(new AssetForListing(item, index, previousAssetID));
@ -295,7 +295,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
// But even for Fair bots, we should track and skip sets where we don't have any item to trade with
if (!matchEverything) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (tradableSets.TryGetValue(key, out bool tradable)) {
if (!tradable && item.Tradable) {
@ -377,12 +377,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
if (!matchEverything) {
// We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data
HashSet<uint> realAppIDs = [];
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> state = new();
foreach (AssetForListing asset in assetsForListing) {
realAppIDs.Add(asset.RealAppID);
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
if (state.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount;
@ -448,11 +448,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
return;
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
Dictionary<ulong, uint> setCopy = [];
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in state) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in state) {
if (!databaseSets.TryGetValue(key, out HashSet<ulong>? databaseSet)) {
// We have no clue about this set, we can't do any optimization
continue;
@ -490,7 +490,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HashSet<AssetForListing> assetsForListingFiltered = [];
foreach (AssetForListing asset in assetsForListing.Where(asset => state.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable).ThenByDescending(static asset => asset.Index)) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
if (!state.TryGetValue(key, out Dictionary<ulong, uint>? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) {
// We're not interested in this combination
@ -903,7 +903,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
return;
}
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
Bot.ArchiLogger.LogNullError(acceptedMatchableTypes);
@ -939,7 +939,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HashSet<Asset> assetsForMatching;
try {
assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
} catch (HttpRequestException e) {
Bot.ArchiLogger.LogGenericWarningException(e);
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(assetsForMatching)));
@ -959,7 +959,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
// Remove from our inventory items that can't be possibly matched due to no dupes to offer available
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet();
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet();
if (assetsForMatching.RemoveWhere(item => !setsToKeep.Contains((item.RealAppID, item.Type, item.Rarity))) > 0) {
if (assetsForMatching.Count == 0) {
@ -971,12 +971,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
// We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data
HashSet<uint> realAppIDs = [];
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> setsState = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> setsState = new();
foreach (Asset asset in assetsForMatching) {
realAppIDs.Add(asset.RealAppID);
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
if (setsState.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount;
@ -1033,11 +1033,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
return;
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
Dictionary<ulong, uint> setCopy = [];
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
uint minimumAmount = uint.MaxValue;
uint maximumAmount = uint.MinValue;
@ -1096,7 +1096,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HashSet<Asset> assetsForMatchingFiltered = [];
foreach (Asset asset in assetsForMatching.Where(asset => setsState.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable)) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
if (!setsState.TryGetValue(key, out Dictionary<ulong, uint>? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) {
// We're not interested in this combination
@ -1167,7 +1167,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
}
private async Task<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
private async Task<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<EAssetType> acceptedMatchableTypes) {
if ((listedUsers == null) || (listedUsers.Count == 0)) {
throw new ArgumentNullException(nameof(listedUsers));
}
@ -1180,7 +1180,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
}
(Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourAssets);
(Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourAssets);
if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) {
// User doesn't have any more dupes in the inventory
@ -1263,7 +1263,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
break;
}
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
if (wantedSets.Count == 0) {
continue;
@ -1284,26 +1284,26 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
continue;
}
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
if (theirInventory.Count == 0) {
continue;
}
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisUser = [];
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisUser = [];
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> theirTradableState = Trading.GetTradableInventoryState(theirInventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> theirTradableState = Trading.GetTradableInventoryState(theirInventory);
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
byte itemsInTrade = 0;
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisTrade = [];
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisTrade = [];
Dictionary<ulong, uint> classIDsToGive = new();
Dictionary<ulong, uint> classIDsToReceive = new();
Dictionary<ulong, uint> fairClassIDsToGive = new();
Dictionary<ulong, uint> fairClassIDsToReceive = new();
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) {
if (!ourTradableState.TryGetValue(set, out Dictionary<ulong, uint>? ourTradableItems) || (ourTradableItems.Count == 0)) {
// We may have no more tradable items from this set
continue;
@ -1506,10 +1506,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
// However, since this is only an assumption, we must mark newly acquired items as untradable so we're sure that they're not considered for trading, only for matching
foreach (Asset itemToReceive in itemsToReceive) {
if (ourInventory.TryGetValue(itemToReceive.AssetID, out Asset? item)) {
item.Description.ProtobufBody.tradable = false;
item.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity);
item.Description.Body.tradable = false;
item.Amount += itemToReceive.Amount;
} else {
itemToReceive.Description.ProtobufBody.tradable = false;
itemToReceive.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity);
itemToReceive.Description.Body.tradable = false;
ourInventory[itemToReceive.AssetID] = itemToReceive;
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -45,7 +45,7 @@ public sealed class Bot {
foreach ((uint appID, byte cards) in itemsPerSet) {
for (byte i = 1; i <= cards; i++) {
items.Add(CreateCard(i, appID));
items.Add(CreateCard(i, realAppID: appID));
}
}
@ -61,8 +61,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID),
CreateCard(2, appID)
CreateCard(1, realAppID: appID),
CreateCard(2, realAppID: appID)
];
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
@ -73,10 +73,10 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(3, appID)
CreateCard(1, realAppID: appID),
CreateCard(1, realAppID: appID),
CreateCard(2, realAppID: appID),
CreateCard(3, realAppID: appID)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
@ -95,10 +95,10 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(2, appID)
CreateCard(1, realAppID: appID),
CreateCard(1, realAppID: appID),
CreateCard(2, realAppID: appID),
CreateCard(2, realAppID: appID)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -116,9 +116,9 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, 2),
CreateCard(2, appID),
CreateCard(2, appID)
CreateCard(1, amount: 2, realAppID: appID),
CreateCard(2, realAppID: appID),
CreateCard(2, realAppID: appID)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -136,27 +136,27 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common),
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon),
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare),
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare),
// for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
CreateCard(3, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
CreateCard(7, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
CreateCard(2, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare),
CreateCard(3, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare),
CreateCard(4, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
@ -177,8 +177,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID),
CreateCard(2, appID)
CreateCard(1, realAppID: appID),
CreateCard(2, realAppID: appID)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
@ -192,8 +192,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID),
CreateCard(2, appID)
CreateCard(1, realAppID: appID),
CreateCard(2, realAppID: appID)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -212,8 +212,8 @@ public sealed class Bot {
const uint appID1 = 43;
HashSet<Asset> items = [
CreateCard(1, appID0),
CreateCard(1, appID1)
CreateCard(1, realAppID: appID0),
CreateCard(1, realAppID: appID1)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
@ -237,8 +237,8 @@ public sealed class Bot {
const uint appID1 = 43;
HashSet<Asset> items = [
CreateCard(1, appID0),
CreateCard(1, appID1)
CreateCard(1, realAppID: appID0),
CreateCard(1, realAppID: appID1)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
@ -260,12 +260,12 @@ public sealed class Bot {
const uint appID2 = 44;
HashSet<Asset> items = [
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(1, realAppID: appID0),
CreateCard(2, realAppID: appID0),
CreateCard(1, appID1),
CreateCard(2, appID1),
CreateCard(3, appID1)
CreateCard(1, realAppID: appID1),
CreateCard(2, realAppID: appID1),
CreateCard(3, realAppID: appID1)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
@ -290,8 +290,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
@ -308,8 +308,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -324,11 +324,11 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(2, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
CreateCard(2, realAppID: appID, rarity: EAssetRarity.Common),
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Uncommon),
CreateCard(2, realAppID: appID, rarity: EAssetRarity.Uncommon),
CreateCard(3, realAppID: appID, rarity: EAssetRarity.Uncommon)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
@ -347,8 +347,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
@ -365,8 +365,8 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -381,11 +381,11 @@ public sealed class Bot {
const uint appID = 42;
HashSet<Asset> items = [
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(2, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard),
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard),
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard),
CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
@ -404,8 +404,8 @@ public sealed class Bot {
const uint appID0 = 42;
HashSet<Asset> items = [
CreateCard(1, appID0, 2),
CreateCard(2, appID0)
CreateCard(1, amount: 2, realAppID: appID0),
CreateCard(2, realAppID: appID0)
];
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
@ -425,8 +425,8 @@ public sealed class Bot {
HashSet<Asset> items = [];
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
items.Add(CreateCard(1, appID));
items.Add(CreateCard(2, appID));
items.Add(CreateCard(1, realAppID: appID));
items.Add(CreateCard(2, realAppID: appID));
}
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
@ -442,10 +442,10 @@ public sealed class Bot {
HashSet<Asset> items = [];
for (byte i = 0; i < 100; i++) {
items.Add(CreateCard(1, appID0));
items.Add(CreateCard(2, appID0));
items.Add(CreateCard(1, appID1));
items.Add(CreateCard(2, appID1));
items.Add(CreateCard(1, realAppID: appID0));
items.Add(CreateCard(2, realAppID: appID0));
items.Add(CreateCard(1, realAppID: appID1));
items.Add(CreateCard(2, realAppID: appID1));
}
Dictionary<uint, byte> itemsPerSet = new() {
@ -465,10 +465,10 @@ public sealed class Bot {
const uint appID2 = 44;
HashSet<Asset> items = [
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(3, appID0),
CreateCard(4, appID0)
CreateCard(1, realAppID: appID0),
CreateCard(2, realAppID: appID0),
CreateCard(3, realAppID: appID0),
CreateCard(4, realAppID: appID0)
];
Assert.ThrowsException<InvalidOperationException>(
@ -491,12 +491,12 @@ public sealed class Bot {
Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality)));
}
private static Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(), realAppID, type, rarity);
private static Asset CreateCard(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity));
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) {
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
return GetItemsForFullSets(inventory, inventorySets.ToDictionary(static kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet();
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -33,18 +33,18 @@ public sealed class Trading {
[TestMethod]
public void ExploitingNewSetsIsFairButNotNeutral() {
HashSet<Asset> inventory = [
CreateItem(1, 40),
CreateItem(2, 10),
CreateItem(3, 10)
CreateItem(1, amount: 40),
CreateItem(2, amount: 10),
CreateItem(3, amount: 10)
];
HashSet<Asset> itemsToGive = [
CreateItem(2, 5),
CreateItem(3, 5)
CreateItem(2, amount: 5),
CreateItem(3, amount: 5)
];
HashSet<Asset> itemsToReceive = [
CreateItem(1, 9),
CreateItem(1, amount: 9),
CreateItem(4)
];
@ -54,24 +54,39 @@ public sealed class Trading {
[TestMethod]
public void MismatchRarityIsNotFair() {
HashSet<Asset> itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(1, rarity: EAssetRarity.Rare)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchRealAppIDsIsNotFair() {
HashSet<Asset> itemsToGive = [CreateItem(1, realAppID: 570)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(1, realAppID: 570)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchTypesIsNotFair() {
HashSet<Asset> itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(1, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
@ -79,19 +94,19 @@ public sealed class Trading {
[TestMethod]
public void MultiGameMultiTypeBadReject() {
HashSet<Asset> inventory = [
CreateItem(1, 9),
CreateItem(3, 9, 730, Asset.EType.Emoticon),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(1, amount: 9),
CreateItem(3, amount: 9, realAppID: 730, type: EAssetType.Emoticon),
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToGive = [
CreateItem(1),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
@ -101,18 +116,18 @@ public sealed class Trading {
[TestMethod]
public void MultiGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = [
CreateItem(1, 9),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(1, amount: 9),
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToGive = [
CreateItem(1),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
@ -122,7 +137,7 @@ public sealed class Trading {
[TestMethod]
public void MultiGameSingleTypeBadReject() {
HashSet<Asset> inventory = [
CreateItem(1, 9),
CreateItem(1, amount: 9),
CreateItem(3, realAppID: 730),
CreateItem(4, realAppID: 730)
];
@ -144,7 +159,7 @@ public sealed class Trading {
[TestMethod]
public void MultiGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = [
CreateItem(1, 2),
CreateItem(1, amount: 2),
CreateItem(3, realAppID: 730)
];
@ -166,15 +181,19 @@ public sealed class Trading {
public void SingleGameAbrynosWasWrongNeutralAccept() {
HashSet<Asset> inventory = [
CreateItem(1),
CreateItem(2, 2),
CreateItem(2, amount: 2),
CreateItem(3),
CreateItem(4),
CreateItem(5)
];
HashSet<Asset> itemsToGive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(2)
];
HashSet<Asset> itemsToReceive = [CreateItem(3)];
HashSet<Asset> itemsToReceive = [
CreateItem(3)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -182,13 +201,17 @@ public sealed class Trading {
[TestMethod]
public void SingleGameDonationAccept() {
HashSet<Asset> inventory = [CreateItem(1)];
HashSet<Asset> inventory = [
CreateItem(1)
];
HashSet<Asset> itemsToGive = [CreateItem(1)];
HashSet<Asset> itemsToGive = [
CreateItem(1)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2),
CreateItem(3, type: Asset.EType.SteamGems)
CreateItem(3, type: EAssetType.SteamGems)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
@ -198,19 +221,19 @@ public sealed class Trading {
[TestMethod]
public void SingleGameMultiTypeBadReject() {
HashSet<Asset> inventory = [
CreateItem(1, 9),
CreateItem(3, 9, type: Asset.EType.Emoticon),
CreateItem(4, type: Asset.EType.Emoticon)
CreateItem(1, amount: 9),
CreateItem(3, amount: 9, type: EAssetType.Emoticon),
CreateItem(4, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToGive = [
CreateItem(1),
CreateItem(4, type: Asset.EType.Emoticon)
CreateItem(4, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2),
CreateItem(3, type: Asset.EType.Emoticon)
CreateItem(3, type: EAssetType.Emoticon)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
@ -220,18 +243,18 @@ public sealed class Trading {
[TestMethod]
public void SingleGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = [
CreateItem(1, 9),
CreateItem(3, type: Asset.EType.Emoticon)
CreateItem(1, amount: 9),
CreateItem(3, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToGive = [
CreateItem(1),
CreateItem(3, type: Asset.EType.Emoticon)
CreateItem(3, type: EAssetType.Emoticon)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2),
CreateItem(4, type: Asset.EType.Emoticon)
CreateItem(4, type: EAssetType.Emoticon)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
@ -252,7 +275,9 @@ public sealed class Trading {
CreateItem(3)
];
HashSet<Asset> itemsToReceive = [CreateItem(4, 3)];
HashSet<Asset> itemsToReceive = [
CreateItem(4, amount: 3)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -262,15 +287,17 @@ public sealed class Trading {
public void SingleGameQuantityBadReject2() {
HashSet<Asset> inventory = [
CreateItem(1),
CreateItem(2, 2)
CreateItem(2, amount: 2)
];
HashSet<Asset> itemsToGive = [
CreateItem(1),
CreateItem(2, 2)
CreateItem(2, amount: 2)
];
HashSet<Asset> itemsToReceive = [CreateItem(3, 3)];
HashSet<Asset> itemsToReceive = [
CreateItem(3, amount: 3)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -279,7 +306,7 @@ public sealed class Trading {
[TestMethod]
public void SingleGameQuantityNeutralAccept() {
HashSet<Asset> inventory = [
CreateItem(1, 2),
CreateItem(1, amount: 2),
CreateItem(2)
];
@ -288,7 +315,9 @@ public sealed class Trading {
CreateItem(2)
];
HashSet<Asset> itemsToReceive = [CreateItem(3, 2)];
HashSet<Asset> itemsToReceive = [
CreateItem(3, amount: 2)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -301,8 +330,13 @@ public sealed class Trading {
CreateItem(2)
];
HashSet<Asset> itemsToGive = [CreateItem(1)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(1)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -311,12 +345,14 @@ public sealed class Trading {
[TestMethod]
public void SingleGameSingleTypeBadWithOverpayingReject() {
HashSet<Asset> inventory = [
CreateItem(1, 2),
CreateItem(2, 2),
CreateItem(3, 2)
CreateItem(1, amount: 2),
CreateItem(2, amount: 2),
CreateItem(3, amount: 2)
];
HashSet<Asset> itemsToGive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(2)
];
HashSet<Asset> itemsToReceive = [
CreateItem(1),
@ -331,12 +367,17 @@ public sealed class Trading {
public void SingleGameSingleTypeBigDifferenceAccept() {
HashSet<Asset> inventory = [
CreateItem(1),
CreateItem(2, 5),
CreateItem(2, amount: 5),
CreateItem(3)
];
HashSet<Asset> itemsToGive = [CreateItem(2)];
HashSet<Asset> itemsToReceive = [CreateItem(3)];
HashSet<Asset> itemsToGive = [
CreateItem(2)
];
HashSet<Asset> itemsToReceive = [
CreateItem(3)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -346,10 +387,10 @@ public sealed class Trading {
public void SingleGameSingleTypeBigDifferenceReject() {
HashSet<Asset> inventory = [
CreateItem(1),
CreateItem(2, 2),
CreateItem(3, 2),
CreateItem(4, 3),
CreateItem(5, 10)
CreateItem(2, amount: 2),
CreateItem(3, amount: 2),
CreateItem(4, amount: 3),
CreateItem(5, amount: 10)
];
HashSet<Asset> itemsToGive = [
@ -368,9 +409,17 @@ public sealed class Trading {
[TestMethod]
public void SingleGameSingleTypeGoodAccept() {
HashSet<Asset> inventory = [CreateItem(1, 2)];
HashSet<Asset> itemsToGive = [CreateItem(1)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> inventory = [
CreateItem(1, amount: 2)
];
HashSet<Asset> itemsToGive = [
CreateItem(1)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -378,9 +427,17 @@ public sealed class Trading {
[TestMethod]
public void SingleGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = [CreateItem(1)];
HashSet<Asset> itemsToGive = [CreateItem(1)];
HashSet<Asset> itemsToReceive = [CreateItem(2)];
HashSet<Asset> inventory = [
CreateItem(1)
];
HashSet<Asset> itemsToGive = [
CreateItem(1)
];
HashSet<Asset> itemsToReceive = [
CreateItem(2)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
@ -389,11 +446,13 @@ public sealed class Trading {
[TestMethod]
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
HashSet<Asset> inventory = [
CreateItem(1, 2),
CreateItem(2, 2)
CreateItem(1, amount: 2),
CreateItem(2, amount: 2)
];
HashSet<Asset> itemsToGive = [CreateItem(2)];
HashSet<Asset> itemsToGive = [
CreateItem(2)
];
HashSet<Asset> itemsToReceive = [
CreateItem(1),
@ -407,26 +466,28 @@ public sealed class Trading {
[TestMethod]
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
HashSet<Asset> inventory = [
CreateItem(1, 52),
CreateItem(2, 73),
CreateItem(3, 52),
CreateItem(4, 47),
CreateItem(1, amount: 52),
CreateItem(2, amount: 73),
CreateItem(3, amount: 52),
CreateItem(4, amount: 47),
CreateItem(5)
];
HashSet<Asset> itemsToGive = [CreateItem(2, 73)];
HashSet<Asset> itemsToGive = [
CreateItem(2, amount: 73)
];
HashSet<Asset> itemsToReceive = [
CreateItem(1, 9),
CreateItem(3, 9),
CreateItem(4, 8),
CreateItem(5, 24),
CreateItem(6, 23)
CreateItem(1, amount: 9),
CreateItem(3, amount: 9),
CreateItem(4, amount: 8),
CreateItem(5, amount: 24),
CreateItem(6, amount: 23)
];
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
private static Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Asset.SteamAppID, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(), realAppID, type, rarity);
private static Asset CreateItem(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity));
}

View file

@ -719,16 +719,16 @@
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |&#xD;
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|&#xD;
----------------------------------------------------------------------------------------------&#xD;
&#xD;
|&#xD;
Copyright 2015-${CurrentDate.Year} Łukasz "JustArchi" Domeradzki&#xD;
Contact: JustArchi@JustArchi.net&#xD;
&#xD;
|&#xD;
Licensed under the Apache License, Version 2.0 (the "License");&#xD;
you may not use this file except in compliance with the License.&#xD;
You may obtain a copy of the License at&#xD;
&#xD;
|&#xD;
http://www.apache.org/licenses/LICENSE-2.0&#xD;
&#xD;
|&#xD;
Unless required by applicable law or agreed to in writing, software&#xD;
distributed under the License is distributed on an "AS IS" BASIS,&#xD;
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#xD;

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -684,7 +684,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
public T? GetHandler<T>() where T : ClientMsgHandler => SteamClient.GetHandler<T>();
[PublicAPI]
public static HashSet<Asset> GetItemsForFullSets(IReadOnlyCollection<Asset> inventory, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) {
public static HashSet<Asset> GetItemsForFullSets(IReadOnlyCollection<Asset> inventory, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) {
if ((inventory == null) || (inventory.Count == 0)) {
throw new ArgumentNullException(nameof(inventory));
}
@ -696,9 +696,9 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
ArgumentOutOfRangeException.ThrowIfLessThan(maxItems, MinCardsPerBadge);
HashSet<Asset> result = [];
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, HashSet<Asset>>> itemsPerClassIDPerSet = inventory.GroupBy(static item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(static grouping => grouping.Key, static grouping => grouping.GroupBy(static item => item.ClassID).ToDictionary(static group => group.Key, static group => group.ToHashSet()));
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, HashSet<Asset>>> itemsPerClassIDPerSet = inventory.GroupBy(static item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(static grouping => grouping.Key, static grouping => grouping.GroupBy(static item => item.ClassID).ToDictionary(static group => group.Key, static group => group.ToHashSet()));
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(static kv => kv.Value.ItemsPerSet)) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(static kv => kv.Value.ItemsPerSet)) {
if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary<ulong, HashSet<Asset>>? itemsPerClassID)) {
continue;
}
@ -3662,7 +3662,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
return;
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
// Filter appIDs that can't possibly be completed due to having less cards than smallest badges possible
appIDs.IntersectWith(inventorySets.Where(static kv => kv.Value.Count >= MinCardsPerBadge).Select(static kv => kv.Key.RealAppID));
@ -3677,9 +3677,9 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
return;
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = new();
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, List<uint> amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, List<uint> amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) {
if (!cardsCountPerAppID.TryGetValue(key.RealAppID, out byte cardsCount) || (cardsCount == 0)) {
throw new InvalidOperationException(nameof(cardsCount));
}

View file

@ -5,29 +5,34 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
namespace ArchiSteamFarm.Steam.Data;
public class APIWrappedResponse<T> where T : class {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class APIWrappedResponse<T> where T : class {
[JsonInclude]
[JsonPropertyName("response")]
[JsonRequired]
public T Response { get; private init; } = null!;
[JsonConstructor]
private APIWrappedResponse() { }
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// |
// http://www.apache.org/licenses/LICENSE-2.0
//
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,7 +22,6 @@
// limitations under the License.
using System;
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
@ -45,36 +44,23 @@ public sealed class Asset {
[JsonIgnore]
[PublicAPI]
public bool Marketable => Description.Marketable;
public bool Marketable => Description?.Marketable ?? false;
[JsonIgnore]
[PublicAPI]
public ERarity Rarity => OverriddenRarity ?? Description.Rarity;
public EAssetRarity Rarity => Description?.Rarity ?? EAssetRarity.Unknown;
[JsonIgnore]
[PublicAPI]
public uint RealAppID => OverriddenRealAppID ?? Description.RealAppID;
public uint RealAppID => Description?.RealAppID ?? 0;
[JsonIgnore]
[PublicAPI]
public ImmutableHashSet<Tag> Tags => Description.Tags;
public bool Tradable => Description?.Tradable ?? false;
[JsonIgnore]
[PublicAPI]
public bool Tradable => Description.Tradable;
[JsonIgnore]
[PublicAPI]
public EType Type => OverriddenType ?? Description.Type;
[JsonIgnore]
private ERarity? OverriddenRarity { get; }
[JsonIgnore]
private uint? OverriddenRealAppID { get; }
[JsonIgnore]
private EType? OverriddenType { get; }
public EAssetType Type => Description?.Type ?? EAssetType.Unknown;
[JsonInclude]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
@ -105,8 +91,9 @@ public sealed class Asset {
[PublicAPI]
public ulong ContextID { get; private init; }
[JsonIgnore]
[PublicAPI]
public InventoryDescription Description { get; internal set; } = null!;
public InventoryDescription? Description { get; internal set; }
[JsonInclude]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
@ -122,61 +109,25 @@ public sealed class Asset {
init => AssetID = value;
}
internal Asset(uint appID, ulong contextID, ulong classID, uint amount, InventoryDescription description, uint realAppID, EType? type, ERarity? rarity, ulong assetID = 0, ulong instanceID = 0) : this(appID, contextID, classID, amount, description, assetID, instanceID) {
ArgumentOutOfRangeException.ThrowIfZero(realAppID);
OverriddenRealAppID = realAppID;
OverriddenType = type;
OverriddenRarity = rarity;
}
internal Asset(uint appID, ulong contextID, ulong classID, uint amount, InventoryDescription description, ulong assetID = 0, ulong instanceID = 0) {
ArgumentOutOfRangeException.ThrowIfZero(appID);
ArgumentOutOfRangeException.ThrowIfZero(contextID);
ArgumentOutOfRangeException.ThrowIfZero(classID);
ArgumentOutOfRangeException.ThrowIfZero(amount);
ArgumentNullException.ThrowIfNull(description);
AppID = appID;
ContextID = contextID;
ClassID = classID;
Amount = amount;
Description = description;
InstanceID = instanceID;
AssetID = assetID;
InstanceID = instanceID;
}
[JsonConstructor]
private Asset() { }
[UsedImplicitly]
public static bool ShouldSerializeAdditionalProperties() => false;
internal Asset CreateShallowCopy() => (Asset) MemberwiseClone();
public enum ERarity : byte {
Unknown,
Common,
Uncommon,
Rare
}
public enum EType : byte {
Unknown,
BoosterPack,
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard,
SteamGems,
SaleItem,
Consumable,
ProfileModifier,
Sticker,
ChatEffect,
MiniProfileBackground,
AvatarProfileFrame,
AnimatedAvatar,
KeyboardSkin,
StartupVideo
}
}

View file

@ -0,0 +1,31 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace ArchiSteamFarm.Steam.Data;
public enum EAssetRarity : byte {
Unknown,
Common,
Uncommon,
Rare
}

View file

@ -0,0 +1,44 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace ArchiSteamFarm.Steam.Data;
public enum EAssetType : byte {
Unknown,
BoosterPack,
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard,
SteamGems,
SaleItem,
Consumable,
ProfileModifier,
Sticker,
ChatEffect,
MiniProfileBackground,
AvatarProfileFrame,
AnimatedAvatar,
KeyboardSkin,
StartupVideo
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// |
// http://www.apache.org/licenses/LICENSE-2.0
//
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,8 +22,8 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
@ -38,140 +38,21 @@ namespace ArchiSteamFarm.Steam.Data;
[PublicAPI]
public sealed class InventoryDescription {
[JsonIgnore]
public CEconItem_Description ProtobufBody { get; } = new();
internal Asset.ERarity Rarity {
get {
foreach (Tag tag in Tags) {
switch (tag.Identifier) {
case "droprate":
switch (tag.Value) {
case "droprate_0":
return Asset.ERarity.Common;
case "droprate_1":
return Asset.ERarity.Uncommon;
case "droprate_2":
return Asset.ERarity.Rare;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
break;
}
}
return Asset.ERarity.Unknown;
}
}
internal uint RealAppID {
get {
foreach (Tag tag in Tags) {
switch (tag.Identifier) {
case "Game":
if (string.IsNullOrEmpty(tag.Value) || (tag.Value.Length <= 4) || !tag.Value.StartsWith("app_", StringComparison.Ordinal)) {
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
}
string appIDText = tag.Value[4..];
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(appID);
break;
}
return appID;
}
}
return 0;
}
}
internal Asset.EType Type {
get {
Asset.EType type = Asset.EType.Unknown;
foreach (Tag tag in Tags) {
switch (tag.Identifier) {
case "cardborder":
switch (tag.Value) {
case "cardborder_0":
return Asset.EType.TradingCard;
case "cardborder_1":
return Asset.EType.FoilTradingCard;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
return Asset.EType.Unknown;
}
case "item_class":
switch (tag.Value) {
case "item_class_2":
if (type == Asset.EType.Unknown) {
// This is a fallback in case we'd have no cardborder available to interpret
type = Asset.EType.TradingCard;
}
continue;
case "item_class_3":
return Asset.EType.ProfileBackground;
case "item_class_4":
return Asset.EType.Emoticon;
case "item_class_5":
return Asset.EType.BoosterPack;
case "item_class_6":
return Asset.EType.Consumable;
case "item_class_7":
return Asset.EType.SteamGems;
case "item_class_8":
return Asset.EType.ProfileModifier;
case "item_class_10":
return Asset.EType.SaleItem;
case "item_class_11":
return Asset.EType.Sticker;
case "item_class_12":
return Asset.EType.ChatEffect;
case "item_class_13":
return Asset.EType.MiniProfileBackground;
case "item_class_14":
return Asset.EType.AvatarProfileFrame;
case "item_class_15":
return Asset.EType.AnimatedAvatar;
case "item_class_16":
return Asset.EType.KeyboardSkin;
case "item_class_17":
return Asset.EType.StartupVideo;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
return Asset.EType.Unknown;
}
}
}
return type;
}
}
public CEconItem_Description Body { get; } = new();
[JsonInclude]
[JsonPropertyName("appid")]
[JsonRequired]
public uint AppID {
get => (uint) ProtobufBody.appid;
private init => ProtobufBody.appid = (int) value;
get => (uint) Body.appid;
private init => Body.appid = (int) value;
}
[JsonInclude]
[JsonPropertyName("background_color")]
public string BackgroundColor {
get => ProtobufBody.background_color;
private init => ProtobufBody.background_color = value;
get => Body.background_color;
private init => Body.background_color = value;
}
[JsonInclude]
@ -179,70 +60,71 @@ public sealed class InventoryDescription {
[JsonPropertyName("classid")]
[JsonRequired]
public ulong ClassID {
get => ProtobufBody.classid;
private init => ProtobufBody.classid = value;
get => Body.classid;
private init => Body.classid = value;
}
[JsonInclude]
[JsonPropertyName("commodity")]
[JsonConverter(typeof(BooleanNumberConverter))]
public bool Commodity {
get => ProtobufBody.commodity;
private init => ProtobufBody.commodity = value;
get => Body.commodity;
private init => Body.commodity = value;
}
[JsonInclude]
[JsonPropertyName("currency")]
[JsonConverter(typeof(BooleanNumberConverter))]
public bool Currency {
get => ProtobufBody.currency;
private init => ProtobufBody.currency = value;
get => Body.currency;
private init => Body.currency = value;
}
[JsonInclude]
[JsonPropertyName("descriptions")]
public ImmutableHashSet<ItemDescription> Descriptions {
get => ProtobufBody.descriptions.Select(static description => new ItemDescription(description.type, description.value, description.color, description.label)).ToImmutableHashSet();
private init {
ProtobufBody.descriptions.Clear();
get => Body.descriptions.Select(static description => new ItemDescription(description.type, description.value, description.color, description.label)).ToImmutableHashSet();
foreach (ItemDescription description in value) {
ProtobufBody.descriptions.Add(
new CEconItem_DescriptionLine {
private init {
Body.descriptions.Clear();
Body.descriptions.AddRange(
value.Select(
static description => new CEconItem_DescriptionLine {
color = description.Color,
label = description.Label,
type = description.Type,
value = description.Value
}
);
}
)
);
}
}
#pragma warning disable CA1056 // This property is not guaranteed to have parsable Uri
[JsonInclude]
[JsonPropertyName("icon_url")]
#pragma warning disable CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL
public string IconURL {
#pragma warning restore CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL
get => ProtobufBody.icon_url;
private init => ProtobufBody.icon_url = value;
get => Body.icon_url;
private init => Body.icon_url = value;
}
#pragma warning restore CA1056 // This property is not guaranteed to have parsable Uri
#pragma warning disable CA1056 // This property is not guaranteed to have parsable Uri
[JsonInclude]
[JsonPropertyName("icon_url_large")]
#pragma warning disable CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL
public string IconURLLarge {
#pragma warning restore CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL
get => ProtobufBody.icon_url_large;
private init => ProtobufBody.icon_url_large = value;
get => Body.icon_url_large;
private init => Body.icon_url_large = value;
}
#pragma warning restore CA1056 // This property is not guaranteed to have parsable Uri
[JsonInclude]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
[JsonPropertyName("instanceid")]
public ulong InstanceID {
get => ProtobufBody.instanceid;
private init => ProtobufBody.instanceid = value;
get => Body.instanceid;
private init => Body.instanceid = value;
}
[JsonInclude]
@ -250,74 +132,163 @@ public sealed class InventoryDescription {
[JsonRequired]
[JsonConverter(typeof(BooleanNumberConverter))]
public bool Marketable {
get => ProtobufBody.marketable;
private init => ProtobufBody.marketable = value;
get => Body.marketable;
private init => Body.marketable = value;
}
[JsonInclude]
[JsonPropertyName("market_fee_app")]
public uint MarketFeeApp {
get => (uint) ProtobufBody.market_fee_app;
private init => ProtobufBody.market_fee_app = (int) value;
get => (uint) Body.market_fee_app;
private init => Body.market_fee_app = (int) value;
}
[JsonInclude]
[JsonPropertyName("market_hash_name")]
public string MarketHashName {
get => ProtobufBody.market_hash_name;
private init => ProtobufBody.market_hash_name = value;
get => Body.market_hash_name;
private init => Body.market_hash_name = value;
}
[JsonInclude]
[JsonPropertyName("market_name")]
public string MarketName {
get => ProtobufBody.market_name;
private init => ProtobufBody.market_name = value;
get => Body.market_name;
private init => Body.market_name = value;
}
[JsonInclude]
[JsonPropertyName("name")]
public string Name {
get => ProtobufBody.name;
private init => ProtobufBody.name = value;
get => Body.name;
private init => Body.name = value;
}
[JsonInclude]
[JsonPropertyName("owner_actions")]
public ImmutableHashSet<ItemAction> OwnerActions {
get => ProtobufBody.owner_actions.Select(static action => new ItemAction(action.link, action.name)).ToImmutableHashSet();
private init {
ProtobufBody.owner_actions.Clear();
get => Body.owner_actions.Select(static action => new ItemAction(action.link, action.name)).ToImmutableHashSet();
foreach (ItemAction action in value) {
ProtobufBody.owner_actions.Add(
new CEconItem_Action {
private init {
Body.owner_actions.Clear();
Body.owner_actions.AddRange(
value.Select(
static action => new CEconItem_Action {
link = action.Link,
name = action.Name
}
);
}
)
);
}
}
[JsonInclude]
[JsonPropertyName("type")]
public string TypeText {
get => ProtobufBody.type;
private init => ProtobufBody.type = value;
get => Body.type;
private init => Body.type = value;
}
internal EAssetRarity Rarity {
get {
if (CachedRarity.HasValue) {
return CachedRarity.Value;
}
foreach (CEconItem_Tag? tag in Body.tags) {
switch (tag.category) {
case "droprate":
switch (tag.internal_name) {
case "droprate_0":
CachedRarity = EAssetRarity.Common;
return CachedRarity.Value;
case "droprate_1":
CachedRarity = EAssetRarity.Uncommon;
return CachedRarity.Value;
case "droprate_2":
CachedRarity = EAssetRarity.Rare;
return CachedRarity.Value;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name));
CachedRarity = EAssetRarity.Unknown;
return CachedRarity.Value;
}
}
}
CachedRarity = EAssetRarity.Unknown;
return CachedRarity.Value;
}
private init {
if (!Enum.IsDefined(value)) {
throw new InvalidEnumArgumentException(nameof(value), (int) value, typeof(EAssetRarity));
}
CachedRarity = value;
}
}
internal uint RealAppID {
get {
if (CachedRealAppID.HasValue) {
return CachedRealAppID.Value;
}
foreach (CEconItem_Tag? tag in Body.tags) {
switch (tag.category) {
case "Game":
if (string.IsNullOrEmpty(tag.internal_name) || (tag.internal_name.Length <= 4) || !tag.internal_name.StartsWith("app_", StringComparison.Ordinal)) {
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name));
break;
}
string appIDText = tag.internal_name[4..];
if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(appID);
break;
}
CachedRealAppID = appID;
return CachedRealAppID.Value;
}
}
CachedRealAppID = 0;
return CachedRealAppID.Value;
}
private init {
ArgumentOutOfRangeException.ThrowIfZero(value);
CachedRealAppID = value;
}
}
[JsonDisallowNull]
[JsonInclude]
[JsonPropertyName("tags")]
internal ImmutableHashSet<Tag> Tags {
get => ProtobufBody.tags.Select(static x => new Tag(x.category, x.internal_name, x.localized_category_name, x.localized_tag_name)).ToImmutableHashSet();
private init {
ProtobufBody.tags.Clear();
get => Body.tags.Select(static tag => new Tag(tag.category, tag.internal_name, tag.localized_category_name, tag.localized_tag_name, tag.color)).ToImmutableHashSet();
foreach (Tag tag in value) {
ProtobufBody.tags.Add(
new CEconItem_Tag {
private init {
Body.tags.Clear();
Body.tags.AddRange(
value.Select(
tag => new CEconItem_Tag {
appid = AppID,
category = tag.Identifier,
color = tag.Color,
@ -325,8 +296,8 @@ public sealed class InventoryDescription {
localized_category_name = tag.LocalizedIdentifier,
localized_tag_name = tag.LocalizedValue
}
);
}
)
);
}
}
@ -335,32 +306,160 @@ public sealed class InventoryDescription {
[JsonRequired]
[JsonConverter(typeof(BooleanNumberConverter))]
internal bool Tradable {
get => ProtobufBody.tradable;
private init => ProtobufBody.tradable = value;
get => Body.tradable;
private init => Body.tradable = value;
}
// For stubs and deserialization
[JsonConstructor]
internal InventoryDescription() { }
internal EAssetType Type {
get {
if (CachedType.HasValue) {
return CachedType.Value;
}
// Constructed from trades being received/sent
internal InventoryDescription(uint appID, ulong classID, ulong instanceID, bool marketable, bool tradable, IReadOnlyCollection<Tag>? tags = null) {
ArgumentOutOfRangeException.ThrowIfZero(appID);
ArgumentOutOfRangeException.ThrowIfZero(classID);
EAssetType type = EAssetType.Unknown;
AppID = appID;
ClassID = classID;
InstanceID = instanceID;
Marketable = marketable;
Tradable = tradable;
foreach (CEconItem_Tag? tag in Body.tags) {
switch (tag.category) {
case "cardborder":
switch (tag.internal_name) {
case "cardborder_0":
CachedType = EAssetType.TradingCard;
if (tags?.Count > 0) {
Tags = tags.ToImmutableHashSet();
return CachedType.Value;
case "cardborder_1":
CachedType = EAssetType.FoilTradingCard;
return CachedType.Value;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name));
CachedType = EAssetType.Unknown;
return CachedType.Value;
}
case "item_class":
switch (tag.internal_name) {
case "item_class_2":
if (type == EAssetType.Unknown) {
// This is a fallback in case we'd have no cardborder available to interpret
type = EAssetType.TradingCard;
}
continue;
case "item_class_3":
CachedType = EAssetType.ProfileBackground;
return CachedType.Value;
case "item_class_4":
CachedType = EAssetType.Emoticon;
return CachedType.Value;
case "item_class_5":
CachedType = EAssetType.BoosterPack;
return CachedType.Value;
case "item_class_6":
CachedType = EAssetType.Consumable;
return CachedType.Value;
case "item_class_7":
CachedType = EAssetType.SteamGems;
return CachedType.Value;
case "item_class_8":
CachedType = EAssetType.ProfileModifier;
return CachedType.Value;
case "item_class_10":
CachedType = EAssetType.SaleItem;
return CachedType.Value;
case "item_class_11":
CachedType = EAssetType.Sticker;
return CachedType.Value;
case "item_class_12":
CachedType = EAssetType.ChatEffect;
return CachedType.Value;
case "item_class_13":
CachedType = EAssetType.MiniProfileBackground;
return CachedType.Value;
case "item_class_14":
CachedType = EAssetType.AvatarProfileFrame;
return CachedType.Value;
case "item_class_15":
CachedType = EAssetType.AnimatedAvatar;
return CachedType.Value;
case "item_class_16":
CachedType = EAssetType.KeyboardSkin;
return CachedType.Value;
case "item_class_17":
CachedType = EAssetType.StartupVideo;
return CachedType.Value;
default:
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name));
CachedType = EAssetType.Unknown;
return CachedType.Value;
}
}
}
CachedType = type;
return CachedType.Value;
}
private init {
if (!Enum.IsDefined(value)) {
throw new InvalidEnumArgumentException(nameof(value), (int) value, typeof(EAssetType));
}
CachedType = value;
}
}
internal InventoryDescription(CEconItem_Description description) => ProtobufBody = description;
private EAssetRarity? CachedRarity;
private uint? CachedRealAppID;
private EAssetType? CachedType;
[UsedImplicitly]
public static bool ShouldSerializeAdditionalProperties() => false;
internal InventoryDescription(CEconItem_Description description) {
ArgumentNullException.ThrowIfNull(description);
Body = description;
}
// For self-created stubs
internal InventoryDescription(uint appID, ulong classID, ulong instanceID = 0, bool marketable = false, bool tradable = false, uint realAppID = 0, EAssetType type = EAssetType.Unknown, EAssetRarity rarity = EAssetRarity.Unknown) {
ArgumentOutOfRangeException.ThrowIfZero(appID);
ArgumentOutOfRangeException.ThrowIfZero(classID);
if (!Enum.IsDefined(type)) {
throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(EAssetType));
}
if (!Enum.IsDefined(rarity)) {
throw new InvalidEnumArgumentException(nameof(rarity), (int) rarity, typeof(EAssetRarity));
}
AppID = appID;
ClassID = classID;
InstanceID = instanceID;
Marketable = marketable;
Tradable = tradable;
RealAppID = realAppID;
Type = type;
Rarity = rarity;
}
[JsonConstructor]
private InventoryDescription() { }
}

View file

@ -5,39 +5,45 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Steam.Data;
public class ItemAction {
public sealed class ItemAction {
[JsonInclude]
[JsonPropertyName("link")]
[JsonRequired]
[PublicAPI]
public string Link { get; private init; } = null!;
public string Link { get; private init; } = "";
[JsonInclude]
[JsonPropertyName("name")]
[JsonRequired]
[PublicAPI]
public string Name { get; private init; } = null!;
public string Name { get; private init; } = "";
internal ItemAction(string link, string name) {
ArgumentException.ThrowIfNullOrEmpty(link);
ArgumentException.ThrowIfNullOrEmpty(name);
Link = link;
Name = name;
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// |
// http://www.apache.org/licenses/LICENSE-2.0
//
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,12 +22,11 @@
// limitations under the License.
using System.Text.Json.Serialization;
using ArchiSteamFarm.Helpers.Json;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Steam.Data;
public class ItemDescription {
public sealed class ItemDescription {
[JsonInclude]
[JsonPropertyName("color")]
[PublicAPI]
@ -38,19 +37,17 @@ public class ItemDescription {
[PublicAPI]
public string? Label { get; private init; }
[JsonDisallowNull]
[JsonInclude]
[JsonPropertyName("type")]
[PublicAPI]
public string Type { get; private init; } = null!;
public string? Type { get; private init; }
[JsonDisallowNull]
[JsonInclude]
[JsonPropertyName("value")]
[PublicAPI]
public string Value { get; private init; } = null!;
public string? Value { get; private init; }
internal ItemDescription(string type, string value, string color, string label) {
internal ItemDescription(string? type = null, string? value = null, string? color = null, string? label = null) {
Type = type;
Value = value;
Color = color;

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -41,15 +41,13 @@ public sealed class Tag {
[JsonInclude]
[JsonPropertyName("localized_category_name")]
[JsonRequired]
[PublicAPI]
public string LocalizedIdentifier { get; private init; } = "";
public string? LocalizedIdentifier { get; private init; }
[JsonInclude]
[JsonPropertyName("localized_tag_name")]
[JsonRequired]
[PublicAPI]
public string LocalizedValue { get; private init; } = "";
public string? LocalizedValue { get; private init; }
[JsonInclude]
[JsonPropertyName("internal_name")]
@ -57,7 +55,7 @@ public sealed class Tag {
[PublicAPI]
public string Value { get; private init; } = "";
internal Tag(string identifier, string value, string localizedIdentifier, string localizedValue, string? color = null) {
internal Tag(string identifier, string value, string? localizedIdentifier = null, string? localizedValue = null, string? color = null) {
ArgumentException.ThrowIfNullOrEmpty(identifier);
ArgumentNullException.ThrowIfNull(value);

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -74,11 +74,11 @@ public sealed class TradeOffer {
private TradeOffer() { }
[PublicAPI]
public bool IsValidSteamItemsRequest(IReadOnlyCollection<Asset.EType> acceptedTypes) {
public bool IsValidSteamItemsRequest(IReadOnlyCollection<EAssetType> acceptedTypes) {
if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) {
throw new ArgumentNullException(nameof(acceptedTypes));
}
return ItemsToGive.All(item => item is { AppID: Asset.SteamAppID, ContextID: Asset.SteamCommunityContextID, AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0 and not Asset.SteamAppID, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown } && acceptedTypes.Contains(item.Type));
return ItemsToGive.All(item => item is { AppID: Asset.SteamAppID, ContextID: Asset.SteamCommunityContextID, AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0 and not Asset.SteamAppID, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown } && acceptedTypes.Contains(item.Type));
}
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,12 +22,14 @@
// limitations under the License.
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using ArchiSteamFarm.Helpers.Json;
namespace ArchiSteamFarm.Steam.Data;
public class TradeOffersResponse {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class TradeOffersResponse {
[JsonDisallowNull]
[JsonInclude]
[JsonPropertyName("descriptions")]
@ -42,4 +44,7 @@ public class TradeOffersResponse {
[JsonInclude]
[JsonPropertyName("trade_offers_sent")]
public ImmutableHashSet<TradeOffer> TradeOffersSent { get; private init; } = [];
[JsonConstructor]
private TradeOffersResponse() { }
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -60,12 +60,12 @@ public sealed class Trading : IDisposable {
public void Dispose() => TradesSemaphore.Dispose();
[PublicAPI]
public static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> GetInventorySets(IReadOnlyCollection<Asset> inventory) {
public static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> GetInventorySets(IReadOnlyCollection<Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
return sets.ToDictionary(static set => set.Key, static set => set.Value.Values.OrderBy(static amount => amount).ToList());
}
@ -80,22 +80,22 @@ public sealed class Trading : IDisposable {
throw new ArgumentNullException(nameof(itemsToReceive));
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToGiveAmounts = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), uint> itemsToGiveAmounts = new();
foreach (Asset item in itemsToGive) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsToGiveAmounts[key] = itemsToGiveAmounts.GetValueOrDefault(key) + item.Amount;
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToReceiveAmounts = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), uint> itemsToReceiveAmounts = new();
foreach (Asset item in itemsToReceive) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
itemsToReceiveAmounts[key] = itemsToReceiveAmounts.GetValueOrDefault(key) + item.Amount;
}
// Ensure that amount of items to give is at least amount of items to receive (per all fairness factors)
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) {
if (!itemsToReceiveAmounts.TryGetValue(key, out uint amountToReceive) || (amountToGive > amountToReceive)) {
return false;
}
@ -124,7 +124,7 @@ public sealed class Trading : IDisposable {
// All of those cases should be verified by our unit tests to ensure that the logic here matches all possible cases, especially those that were incorrectly handled previously
// Firstly we get initial sets state of our inventory
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> initialSets = GetInventorySets(inventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> initialSets = GetInventorySets(inventory);
// Once we have initial state, we remove items that we're supposed to give from our inventory
// This loop is a bit more complex due to the fact that we might have a mix of the same item splitted into different amounts
@ -162,10 +162,10 @@ public sealed class Trading : IDisposable {
}
// Now we can get final sets state of our inventory after the exchange
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> finalSets = GetInventorySets(inventory);
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> finalSets = GetInventorySets(inventory);
// Once we have both states, we can check overall fairness
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
if (!finalSets.TryGetValue(set, out List<uint>? afterAmounts)) {
// If we have no info about this set, then it has to be a bad one
return false;
@ -200,16 +200,16 @@ public sealed class Trading : IDisposable {
return true;
}
internal static (Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Asset> inventory) {
internal static (Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState = new();
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState = new();
foreach (Asset item in inventory) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (fullState.TryGetValue(key, out Dictionary<ulong, uint>? fullSet)) {
fullSet[item.ClassID] = fullSet.GetValueOrDefault(item.ClassID) + item.Amount;
@ -231,15 +231,15 @@ public sealed class Trading : IDisposable {
return (fullState, tradableState);
}
internal static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Asset> inventory) {
internal static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState = new();
foreach (Asset item in inventory.Where(static item => item.Tradable)) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint>? tradableSet)) {
tradableSet[item.ClassID] = tradableSet.GetValueOrDefault(item.ClassID) + item.Amount;
@ -295,11 +295,11 @@ public sealed class Trading : IDisposable {
return result;
}
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState) {
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState) {
ArgumentNullException.ThrowIfNull(fullState);
ArgumentNullException.ThrowIfNull(tradableState);
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, IReadOnlyDictionary<ulong, uint> state) in tradableState) {
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, IReadOnlyDictionary<ulong, uint> state) in tradableState) {
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint>? fullSet) || (fullSet.Count == 0)) {
throw new InvalidOperationException(nameof(fullSet));
}
@ -382,15 +382,15 @@ public sealed class Trading : IDisposable {
}
}
private static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Asset> inventory) {
private static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new();
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> state = new();
foreach (Asset item in inventory) {
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (state.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
set[item.ClassID] = set.GetValueOrDefault(item.ClassID) + item.Amount;
@ -621,7 +621,7 @@ public sealed class Trading : IDisposable {
// If user has a trade hold, we add extra logic
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
case > 0 when (holdDuration.Value > (ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration)) || tradeOffer.ItemsToGive.Any(static item => item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)):
case > 0 when (holdDuration.Value > (ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration)) || tradeOffer.ItemsToGive.Any(static item => item.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)):
Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, $"{nameof(holdDuration)} > 0: {holdDuration.Value}"));
return ParseTradeResult.EResult.Rejected;
@ -635,7 +635,7 @@ public sealed class Trading : IDisposable {
}
// Get sets we're interested in
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = tradeOffer.ItemsToGive.Select(static item => (item.RealAppID, item.Type, item.Rarity)).ToHashSet();
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> wantedSets = tradeOffer.ItemsToGive.Select(static item => (item.RealAppID, item.Type, item.Rarity)).ToHashSet();
// Now check if it's worth for us to do the trade
HashSet<Asset> inventory;

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -160,16 +160,18 @@ public sealed class ArchiHandler : ClientMsgHandler {
public async IAsyncEnumerable<Asset> GetMyInventoryAsync(uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID, bool tradableOnly = false, bool marketableOnly = false, ushort itemsCountPerRequest = 40000) {
ArgumentOutOfRangeException.ThrowIfZero(appID);
ArgumentOutOfRangeException.ThrowIfZero(contextID);
ArgumentOutOfRangeException.ThrowIfZero(itemsCountPerRequest);
if (Client.SteamID == null) {
throw new InvalidOperationException(nameof(Client.SteamID));
}
SteamID steamID = Client.SteamID;
ulong steamID = Client.SteamID;
ulong startAssetID = 0;
// We need to store asset IDs to make sure we won't get duplicate items
HashSet<ulong>? assetIDs = null;
ulong startAssetID = 0;
while (true) {
ulong currentStartAssetID = startAssetID;
@ -177,20 +179,19 @@ public sealed class ArchiHandler : ClientMsgHandler {
CEcon_GetInventoryItemsWithDescriptions_Request request = new() {
appid = appID,
contextid = contextID,
filters = new CEcon_GetInventoryItemsWithDescriptions_Request.FilterOptions {
tradable_only = tradableOnly,
marketable_only = marketableOnly
},
get_descriptions = true,
steamid = steamID.ConvertToUInt64(),
steamid = steamID,
start_assetid = currentStartAssetID,
count = itemsCountPerRequest
};
SteamUnifiedMessages.ServiceMethodResponse genericResponse = await UnifiedEconService
.SendMessage(x => x.GetInventoryItemsWithDescriptions(request))
.ToLongRunningTask()
.ConfigureAwait(false);
SteamUnifiedMessages.ServiceMethodResponse genericResponse = await UnifiedEconService.SendMessage(x => x.GetInventoryItemsWithDescriptions(request)).ToLongRunningTask().ConfigureAwait(false);
CEcon_GetInventoryItemsWithDescriptions_Response response = genericResponse.GetDeserializedResponse<CEcon_GetInventoryItemsWithDescriptions_Response>();
@ -213,18 +214,20 @@ public sealed class ArchiHandler : ClientMsgHandler {
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, $"{nameof(response.assets)} || {nameof(response.descriptions)}"));
}
List<InventoryDescription> convertedDescriptions = response.descriptions.Select(static description => new InventoryDescription(description)).ToList();
Dictionary<(ulong ClassID, ulong InstanceID), InventoryDescription> descriptions = new();
foreach (InventoryDescription description in convertedDescriptions) {
if (description.ClassID == 0) {
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.ClassID)));
foreach (CEconItem_Description? description in response.descriptions) {
if (description.classid == 0) {
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.classid)));
}
(ulong ClassID, ulong InstanceID) key = (description.ClassID, description.InstanceID);
(ulong ClassID, ulong InstanceID) key = (description.classid, description.instanceid);
descriptions.TryAdd(key, description);
if (descriptions.ContainsKey(key)) {
continue;
}
descriptions.TryAdd(key, new InventoryDescription(description));
}
foreach (CEcon_Asset? asset in response.assets) {

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
// |
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// |
// http://www.apache.org/licenses/LICENSE-2.0
//
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -232,17 +232,31 @@ public sealed class ArchiWebHandler : IDisposable {
return result;
}
/// <remarks>
/// If targetting inventory of this <see cref="Bot" /> instance, consider using much more efficient <see cref="ArchiHandler.GetMyInventoryAsync" /> instead.
/// This method should be used exclusively for foreign inventories (other users), but in special circumstances it can be used for fetching bot's own inventory as well.
/// </remarks>
[PublicAPI]
public async IAsyncEnumerable<Asset> GetForeignInventoryAsync(ulong steamID, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
public async IAsyncEnumerable<Asset> GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
ArgumentOutOfRangeException.ThrowIfZero(appID);
ArgumentOutOfRangeException.ThrowIfZero(contextID);
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
throw new ArgumentOutOfRangeException(nameof(steamID));
}
if (steamID == 0) {
if (!Initialized) {
byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout;
if (steamID == Bot.SteamID) {
throw new NotSupportedException();
for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (!Initialized) {
throw new HttpRequestException(Strings.WarningFailed);
}
}
steamID = Bot.SteamID;
} else if (!new SteamID(steamID).IsIndividualAccount) {
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(steamID)));
}
if (ASF.InventorySemaphore == null) {
@ -370,16 +384,6 @@ public sealed class ArchiWebHandler : IDisposable {
}
}
[PublicAPI]
[Obsolete($"Use ArchiHandler.{nameof(ArchiHandler.GetMyInventoryAsync)} for getting bot's own inventory or ArchiWebHandler.${nameof(GetForeignInventoryAsync)} in other cases instead.")]
public IAsyncEnumerable<Asset> GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
if ((steamID == 0) || (steamID == Bot.SteamID)) {
return Bot.ArchiHandler.GetMyInventoryAsync(appID, contextID);
}
return GetForeignInventoryAsync(steamID, appID, contextID);
}
[PublicAPI]
public async Task<uint?> GetPointsBalance() {
string? accessToken = Bot.AccessToken;
@ -480,9 +484,9 @@ public sealed class ArchiWebHandler : IDisposable {
string queryString = string.Join('&', arguments.Select(static argument => $"{argument.Key}={HttpUtility.UrlEncode(argument.Value.ToString())}"));
string request = $"/{EconService}/GetTradeOffers/v1/?" + queryString;
Uri request = new(WebAPI.DefaultBaseAddress, $"/{EconService}/GetTradeOffers/v1?{queryString}");
TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject<APIWrappedResponse<TradeOffersResponse>>(new Uri(WebAPI.DefaultBaseAddress, request)).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response;
TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject<APIWrappedResponse<TradeOffersResponse>>(request).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response;
if (response == null) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
@ -2371,9 +2375,13 @@ public sealed class ArchiWebHandler : IDisposable {
}
private static void SetDescriptionsToAssets(IEnumerable<Asset> assets, [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryDescription> descriptions) {
ArgumentNullException.ThrowIfNull(assets);
ArgumentNullException.ThrowIfNull(descriptions);
foreach (Asset asset in assets) {
if (!descriptions.TryGetValue((asset.AppID, asset.ClassID, asset.InstanceID), out InventoryDescription? description)) {
description = new InventoryDescription(asset.AppID, asset.ClassID, asset.InstanceID, true, true);
// No description, deal with it
continue;
}
asset.Description = description;

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -3107,7 +3107,7 @@ public sealed class Commands {
// It'd also make sense to run all of this in parallel, but it seems that Steam has a lot of problems with inventory-related parallel requests | https://steamcommunity.com/groups/archiasf/discussions/1/3559414588264550284/
try {
await foreach (Asset item in Bot.ArchiHandler.GetMyInventoryAsync().Where(static item => item.Type == Asset.EType.BoosterPack).ConfigureAwait(false)) {
await foreach (Asset item in Bot.ArchiHandler.GetMyInventoryAsync().Where(static item => item.Type == EAssetType.BoosterPack).ConfigureAwait(false)) {
if (!await Bot.ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false)) {
completeSuccess = false;
}

View file

@ -5,16 +5,16 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
//
//
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -117,7 +117,7 @@ public sealed class BotConfig {
internal const byte SteamTradeTokenLength = 8;
[PublicAPI]
public static readonly ImmutableHashSet<Asset.EType> DefaultCompleteTypesToSend = ImmutableHashSet<Asset.EType>.Empty;
public static readonly ImmutableHashSet<EAssetType> DefaultCompleteTypesToSend = ImmutableHashSet<EAssetType>.Empty;
[PublicAPI]
public static readonly ImmutableList<EFarmingOrder> DefaultFarmingOrders = ImmutableList<EFarmingOrder>.Empty;
@ -126,16 +126,16 @@ public sealed class BotConfig {
public static readonly ImmutableList<uint> DefaultGamesPlayedWhileIdle = ImmutableList<uint>.Empty;
[PublicAPI]
public static readonly ImmutableHashSet<Asset.EType> DefaultLootableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard);
public static readonly ImmutableHashSet<EAssetType> DefaultLootableTypes = ImmutableHashSet.Create(EAssetType.BoosterPack, EAssetType.FoilTradingCard, EAssetType.TradingCard);
[PublicAPI]
public static readonly ImmutableHashSet<Asset.EType> DefaultMatchableTypes = ImmutableHashSet.Create(Asset.EType.TradingCard);
public static readonly ImmutableHashSet<EAssetType> DefaultMatchableTypes = ImmutableHashSet.Create(EAssetType.TradingCard);
[PublicAPI]
public static readonly ImmutableDictionary<ulong, EAccess> DefaultSteamUserPermissions = ImmutableDictionary<ulong, EAccess>.Empty;
[PublicAPI]
public static readonly ImmutableHashSet<Asset.EType> DefaultTransferableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard);
public static readonly ImmutableHashSet<EAssetType> DefaultTransferableTypes = ImmutableHashSet.Create(EAssetType.BoosterPack, EAssetType.FoilTradingCard, EAssetType.TradingCard);
[JsonInclude]
public bool AcceptGifts { get; private init; } = DefaultAcceptGifts;
@ -145,8 +145,8 @@ public sealed class BotConfig {
[JsonDisallowNull]
[JsonInclude]
[SwaggerValidValues(ValidIntValues = [(int) Asset.EType.FoilTradingCard, (int) Asset.EType.TradingCard])]
public ImmutableHashSet<Asset.EType> CompleteTypesToSend { get; private init; } = DefaultCompleteTypesToSend;
[SwaggerValidValues(ValidIntValues = [(int) EAssetType.FoilTradingCard, (int) EAssetType.TradingCard])]
public ImmutableHashSet<EAssetType> CompleteTypesToSend { get; private init; } = DefaultCompleteTypesToSend;
[JsonInclude]
public string? CustomGamePlayedWhileFarming { get; private init; } = DefaultCustomGamePlayedWhileFarming;
@ -177,11 +177,11 @@ public sealed class BotConfig {
[JsonDisallowNull]
[JsonInclude]
public ImmutableHashSet<Asset.EType> LootableTypes { get; private init; } = DefaultLootableTypes;
public ImmutableHashSet<EAssetType> LootableTypes { get; private init; } = DefaultLootableTypes;
[JsonDisallowNull]
[JsonInclude]
public ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = DefaultMatchableTypes;
public ImmutableHashSet<EAssetType> MatchableTypes { get; private init; } = DefaultMatchableTypes;
[JsonInclude]
public EPersonaStateFlag OnlineFlags { get; private init; } = DefaultOnlineFlags;
@ -260,7 +260,7 @@ public sealed class BotConfig {
[JsonDisallowNull]
[JsonInclude]
public ImmutableHashSet<Asset.EType> TransferableTypes { get; private init; } = DefaultTransferableTypes;
public ImmutableHashSet<EAssetType> TransferableTypes { get; private init; } = DefaultTransferableTypes;
[JsonInclude]
public bool UseLoginKeys { get; private init; } = DefaultUseLoginKeys;
@ -424,13 +424,13 @@ public sealed class BotConfig {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(GamesPlayedWhileIdle), $"{nameof(GamesPlayedWhileIdle.Count)} {GamesPlayedWhileIdle.Count} > {ArchiHandler.MaxGamesPlayedConcurrently}"));
}
foreach (Asset.EType lootableType in LootableTypes.Where(static lootableType => !Enum.IsDefined(lootableType))) {
foreach (EAssetType lootableType in LootableTypes.Where(static lootableType => !Enum.IsDefined(lootableType))) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(LootableTypes), lootableType));
}
HashSet<Asset.EType>? completeTypesToSendValidTypes = null;
HashSet<EAssetType>? completeTypesToSendValidTypes = null;
foreach (Asset.EType completableType in CompleteTypesToSend) {
foreach (EAssetType completableType in CompleteTypesToSend) {
if (!Enum.IsDefined(completableType)) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType));
}
@ -442,7 +442,7 @@ public sealed class BotConfig {
throw new InvalidOperationException(nameof(completeTypesToSendValidValues));
}
completeTypesToSendValidTypes = completeTypesToSendValidValues.ValidIntValues.Select(static value => (Asset.EType) value).ToHashSet();
completeTypesToSendValidTypes = completeTypesToSendValidValues.ValidIntValues.Select(static value => (EAssetType) value).ToHashSet();
}
if (!completeTypesToSendValidTypes.Contains(completableType)) {
@ -450,7 +450,7 @@ public sealed class BotConfig {
}
}
foreach (Asset.EType matchableType in MatchableTypes.Where(static matchableType => !Enum.IsDefined(matchableType))) {
foreach (EAssetType matchableType in MatchableTypes.Where(static matchableType => !Enum.IsDefined(matchableType))) {
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(MatchableTypes), matchableType));
}