Make MatchActively take into account non-tradable items

This commit is contained in:
JustArchi
2018-12-09 18:08:14 +01:00
parent 9c2ced3df1
commit c6c7f35f7e
6 changed files with 206 additions and 70 deletions

View File

@@ -630,6 +630,7 @@ namespace ArchiSteamFarm {
}
asset.RealAppID = description.RealAppID;
asset.Tradable = description.Tradable;
asset.Type = description.Type;
}

View File

@@ -44,6 +44,7 @@ namespace ArchiSteamFarm.Json {
internal ulong ClassID { get; private set; }
internal ulong ContextID { get; private set; }
internal uint RealAppID { get; set; }
internal bool Tradable { get; set; }
internal EType Type { get; set; }
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
@@ -430,47 +431,6 @@ namespace ArchiSteamFarm.Json {
State = state;
}
internal bool IsFairTypesExchange() {
Dictionary<uint, Dictionary<Asset.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Asset.EType, uint>>();
foreach (Asset item in ItemsToGive) {
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Asset.EType, uint> { [item.Type] = item.Amount };
itemsToGivePerGame[item.RealAppID] = itemsPerType;
}
}
Dictionary<uint, Dictionary<Asset.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Asset.EType, uint>>();
foreach (Asset item in ItemsToReceive) {
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Asset.EType, uint> { [item.Type] = item.Amount };
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
}
}
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
foreach (KeyValuePair<uint, Dictionary<Asset.EType, uint>> itemsPerGame in itemsToGivePerGame) {
if (!itemsToReceivePerGame.TryGetValue(itemsPerGame.Key, out Dictionary<Asset.EType, uint> otherItemsPerType)) {
return false;
}
foreach (KeyValuePair<Asset.EType, uint> itemsPerType in itemsPerGame.Value) {
if (!otherItemsPerType.TryGetValue(itemsPerType.Key, out uint otherAmount)) {
return false;
}
if (itemsPerType.Value > otherAmount) {
return false;
}
}
}
return true;
}
internal bool IsValidSteamItemsRequest(IReadOnlyCollection<Asset.EType> acceptedTypes) {
if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(acceptedTypes));

View File

@@ -100,14 +100,19 @@ namespace ArchiSteamFarm {
}
}
#if NETFRAMEWORK
internal static void TrimExcess<T1, T2>(this Dictionary<T1, T2> _) { } // no-op
#endif
#if NETFRAMEWORK
internal static async Task<WebSocketReceiveResult> ReceiveAsync(this WebSocket webSocket, byte[] buffer, CancellationToken cancellationToken) => await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(false);
internal static async Task SendAsync(this WebSocket webSocket, byte[] buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => await webSocket.SendAsync(new ArraySegment<byte>(buffer), messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
internal static string[] Split(this string text, char separator, StringSplitOptions options = StringSplitOptions.None) => text.Split(new[] { separator }, options);
internal static void TrimExcess<T1, T2>(this Dictionary<T1, T2> _) { } // no-op
internal static void TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value) {
if (dictionary.ContainsKey(key)) {
return;
}
dictionary.Add(key, value);
}
#endif
}
}

View File

@@ -270,17 +270,17 @@ namespace ArchiSteamFarm {
return false;
}
HashSet<Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, tradable: true, wantedTypes: acceptedMatchableTypes).ConfigureAwait(false);
HashSet<Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, wantedTypes: acceptedMatchableTypes).ConfigureAwait(false);
if ((ourInventory == null) || (ourInventory.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventory)));
return false;
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryState = Trading.GetInventoryState(ourInventory);
(Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) = Trading.GetDividedInventoryState(ourInventory);
if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) {
if (Trading.IsEmptyForMatching(fullState, tradableState)) {
// User doesn't have any more dupes in the inventory
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(fullState) + " || " + nameof(tradableState)));
return false;
}
@@ -296,14 +296,18 @@ namespace ArchiSteamFarm {
foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) {
Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "...");
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: ourInventoryState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false);
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: fullState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false);
if ((theirInventory == null) || (theirInventory.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory)));
continue;
}
// Those 3 collections are on user-basis since we can't be sure that the trade passes through (and therefore we need to keep original state in case of failure)
HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisUser = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> theirInventoryState = Trading.GetInventoryState(theirInventory);
Dictionary<ulong, uint> ourFullSet = new Dictionary<ulong, uint>();
Dictionary<ulong, uint> ourTradableSet = new Dictionary<ulong, uint>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> theirTradableState = Trading.GetInventoryState(theirInventory);
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
byte itemsInTrade = 0;
@@ -312,19 +316,30 @@ namespace ArchiSteamFarm {
Dictionary<ulong, uint> classIDsToGive = new Dictionary<ulong, uint>();
Dictionary<ulong, uint> classIDsToReceive = new Dictionary<ulong, uint>();
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryStateSet in ourInventoryState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
if (!theirInventoryState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> theirItems)) {
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryStateSet in fullState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
if (!tradableState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> ourTradableItems)) {
continue;
}
if (!theirTradableState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> theirItems)) {
continue;
}
ourFullSet.AddRange(ourInventoryStateSet.Value);
ourTradableSet.AddRange(ourTradableItems);
bool match;
do {
match = false;
foreach (KeyValuePair<ulong, uint> ourItem in ourInventoryStateSet.Value.Where(item => item.Value > 1).OrderByDescending(item => item.Value)) {
foreach (KeyValuePair<ulong, uint> theirItem in theirItems.OrderBy(item => ourInventoryStateSet.Value.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0)) {
if (ourInventoryStateSet.Value.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1)) {
foreach (KeyValuePair<ulong, uint> ourItem in ourFullSet.Where(item => item.Value > 1).OrderByDescending(item => item.Value)) {
if (!ourTradableSet.TryGetValue(ourItem.Key, out uint tradableAmount)) {
continue;
}
foreach (KeyValuePair<ulong, uint> theirItem in theirItems.OrderBy(item => ourFullSet.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0)) {
if (ourFullSet.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1)) {
continue;
}
@@ -333,11 +348,11 @@ namespace ArchiSteamFarm {
// Update our state based on given items
classIDsToGive[ourItem.Key] = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1;
ourInventoryStateSet.Value[ourItem.Key] = ourItem.Value - 1;
ourFullSet[ourItem.Key] = ourItem.Value - 1;
// Update our state based on received items
classIDsToReceive[theirItem.Key] = classIDsToReceive.TryGetValue(theirItem.Key, out uint receivedAmount) ? receivedAmount + 1 : 1;
ourInventoryStateSet.Value[theirItem.Key] = ourAmountOfTheirItem + 1;
ourFullSet[theirItem.Key] = ourAmountOfTheirItem + 1;
// Update their state based on taken items
if (theirItems.TryGetValue(theirItem.Key, out uint theirAmount) && (theirAmount > 1)) {
@@ -346,6 +361,12 @@ namespace ArchiSteamFarm {
theirItems.Remove(theirItem.Key);
}
if (tradableAmount > 1) {
ourTradableSet[ourItem.Key] = tradableAmount - 1;
} else {
ourTradableSet.Remove(ourItem.Key);
}
itemsInTrade += 2;
match = true;
@@ -368,8 +389,14 @@ namespace ArchiSteamFarm {
break;
}
HashSet<Steam.Asset> itemsToGive = Trading.GetItemsFromInventory(ourInventory, classIDsToGive);
HashSet<Steam.Asset> itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive);
HashSet<Steam.Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive);
HashSet<Steam.Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive);
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairTypesExchange(itemsToGive, itemsToReceive)) {
// Failsafe
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted));
return false;
}
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) previousAttempt)) {
if (itemsToGive.Select(item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains)) {
@@ -425,14 +452,17 @@ namespace ArchiSteamFarm {
skippedSetsThisRound.UnionWith(skippedSetsThisUser);
foreach ((uint AppID, Steam.Asset.EType Type) skippedSet in skippedSetsThisUser) {
ourInventoryState.Remove(skippedSet);
fullState.Remove(skippedSet);
tradableState.Remove(skippedSet);
}
if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) {
if (Trading.IsEmptyForMatching(fullState, tradableState)) {
// User doesn't have any more dupes in the inventory
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
break;
}
fullState.TrimExcess();
tradableState.TrimExcess();
}
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));

View File

@@ -43,32 +43,72 @@ namespace ArchiSteamFarm {
public void Dispose() => TradesSemaphore.Dispose();
internal static (Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> FullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return (null, null);
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
if (fullState.TryGetValue(key, out Dictionary<ulong, uint> fullSet)) {
if (fullSet.TryGetValue(item.ClassID, out uint amount)) {
fullSet[item.ClassID] = amount + item.Amount;
} else {
fullSet[item.ClassID] = item.Amount;
}
} else {
fullState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
}
if (!item.Tradable) {
continue;
}
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint> tradableSet)) {
if (tradableSet.TryGetValue(item.ClassID, out uint amount)) {
tradableSet[item.ClassID] = amount + item.Amount;
} else {
tradableSet[item.ClassID] = item.Amount;
}
} else {
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
}
}
return (fullState, tradableState);
}
internal static Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
}
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> sets = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> state = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
if (sets.TryGetValue(key, out Dictionary<ulong, uint> set)) {
if (state.TryGetValue(key, out Dictionary<ulong, uint> set)) {
if (set.TryGetValue(item.ClassID, out uint amount)) {
set[item.ClassID] = amount + item.Amount;
} else {
set[item.ClassID] = item.Amount;
}
} else {
sets[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
state[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
}
}
return sets;
return state;
}
internal static HashSet<Steam.Asset> GetItemsFromInventory(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<ulong, uint> classIDs) {
internal static HashSet<Steam.Asset> GetTradableItemsFromInventory(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<ulong, uint> classIDs) {
if ((inventory == null) || (inventory.Count == 0) || (classIDs == null) || (classIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(classIDs));
return null;
@@ -76,7 +116,7 @@ namespace ArchiSteamFarm {
HashSet<Steam.Asset> result = new HashSet<Steam.Asset>();
foreach (Steam.Asset item in inventory) {
foreach (Steam.Asset item in inventory.Where(item => item.Tradable)) {
if (!classIDs.TryGetValue(item.ClassID, out uint amount)) {
continue;
}
@@ -97,6 +137,95 @@ namespace ArchiSteamFarm {
return result;
}
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) {
if ((fullState == null) || (tradableState == null)) {
ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState));
return false;
}
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableSet in tradableState) {
foreach (KeyValuePair<ulong, uint> tradableItem in tradableSet.Value) {
switch (tradableItem.Value) {
case 0:
// No tradable items, this should never happen, dictionary should not have this key to begin with
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tradableItem.Value), tradableItem.Value));
return false;
case 1:
// Single tradable item, can be matchable or not depending on the rest of the inventory
if (!fullState.TryGetValue(tradableSet.Key, out Dictionary<ulong, uint> fullItem) || (fullItem == null) || (fullItem.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(fullItem));
return false;
}
if (!fullItem.TryGetValue(tradableItem.Key, out uint fullAmount) || (fullAmount == 0)) {
ASF.ArchiLogger.LogNullError(nameof(fullAmount));
return false;
}
if (fullAmount > 1) {
// If we have a single tradable item but more than 1 in total, this is matchable
return false;
}
// A single exclusive tradable item is not matchable, continue
continue;
default:
// Any other combination of tradable items is always matchable
return false;
}
}
}
// We didn't find any matchable combinations, so this inventory is empty
return true;
}
internal static bool IsFairTypesExchange(IReadOnlyCollection<Steam.Asset> itemsToGive, IReadOnlyCollection<Steam.Asset> itemsToReceive) {
if ((itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
}
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
foreach (Steam.Asset item in itemsToGive) {
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToGivePerGame[item.RealAppID] = itemsPerType;
}
}
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
foreach (Steam.Asset item in itemsToReceive) {
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
} else {
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
}
}
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
foreach (KeyValuePair<uint, Dictionary<Steam.Asset.EType, uint>> itemsPerGame in itemsToGivePerGame) {
if (!itemsToReceivePerGame.TryGetValue(itemsPerGame.Key, out Dictionary<Steam.Asset.EType, uint> otherItemsPerType)) {
return false;
}
foreach (KeyValuePair<Steam.Asset.EType, uint> itemsPerType in itemsPerGame.Value) {
if (!otherItemsPerType.TryGetValue(itemsPerType.Key, out uint otherAmount)) {
return false;
}
if (itemsPerType.Value > otherAmount) {
return false;
}
}
}
return true;
}
internal void OnDisconnected() => IgnoredTrades.Clear();
internal async Task OnNewTrade() {
@@ -365,7 +494,7 @@ namespace ArchiSteamFarm {
}
// Decline trade if we're requested to handle any not-accepted item type or if it's not fair games/types exchange
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !tradeOffer.IsFairTypesExchange()) {
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !IsFairTypesExchange(tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive)) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently, tradeOffer.ItemsToReceive);
}

View File

@@ -36,6 +36,17 @@ namespace ArchiSteamFarm {
// Normally we wouldn't need to use this singleton, but we want to ensure decent randomness across entire program's lifetime
private static readonly Random Random = new Random();
internal static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<TKey, TValue>> other) {
if ((dictionary == null) || (other == null)) {
ASF.ArchiLogger.LogNullError(nameof(dictionary) + " || " + nameof(other));
return;
}
foreach (KeyValuePair<TKey, TValue> item in other) {
dictionary.TryAdd(item.Key, item.Value);
}
}
internal static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
if ((args == null) || (args.Length <= argsToSkip) || string.IsNullOrEmpty(delimiter)) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(argsToSkip) + " || " + nameof(delimiter));