Merge pull request #451 from sp-tarkov/develop

Develop
This commit is contained in:
Chomp
2025-07-05 13:58:49 +01:00
committed by GitHub
212 changed files with 13577 additions and 7346 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
[lfs]
url = https://lfs.sp-tarkov.com/sp-tarkov/server
url = https://lfs.sp-tarkov.com/sp-tarkov/server-csharp
locksverify = false
@@ -2666,9 +2666,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -5557,9 +5557,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -8817,9 +8817,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -12175,9 +12175,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -15893,9 +15893,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -19808,9 +19808,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -23577,9 +23577,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -27471,9 +27471,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -31977,9 +31977,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -36700,9 +36700,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -37693,9 +37693,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -38680,9 +38680,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -41828,9 +41828,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -44961,9 +44961,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -48389,9 +48389,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -51812,9 +51812,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -52795,9 +52795,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -53785,9 +53785,9 @@
},
"WishList": [],
"karmaValue": 0.2,
"_id": "__REPLACEME__",
"_id": "000000000000000000000000",
"aid": "__REPLACEME__",
"savage": "__REPLACEME__"
"savage": "000000000000000000000000"
},
"dialogues": {},
"equipmentBuilds": {},
@@ -1,7 +1,7 @@
{
"templates": {
"Elimination": {
"_id": null,
"_id": "68690637c1394a820efc27ca",
"traderId": "5935c25fb3acc3127c3d8cd9",
"location": null,
"image": "/files/quest/icon/616d993bc8c5ad2ab30ff6ba.jpg",
@@ -30,7 +30,7 @@ public class BotCallbacks(BotController _botController, HttpResponseUtil _httpRe
public ValueTask<string> GetBotDifficulty(string url, EmptyRequestData _, string sessionID)
{
var splitUrl = url.Split('/');
var type = splitUrl[^2].ToLower();
var type = splitUrl[^2].ToLowerInvariant();
var difficulty = splitUrl[^1];
if (difficulty == "core")
{
@@ -1,6 +1,7 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils;
@@ -17,8 +18,7 @@ public class BtrDeliveryCallbacks(
BtrDeliveryService _btrDeliveryService,
TimeUtil _timeUtil,
ConfigServer _configServer,
SaveServer _saveServer,
HashUtil _hashUtil
SaveServer _saveServer
) : IOnUpdate
{
private readonly BtrDeliveryConfig _btrDeliveryConfig =
@@ -110,7 +110,7 @@ public class BtrDeliveryCallbacks(
foreach (var package in packagesToBeDelivered)
{
// Create a new root parent ID for the message we'll be sending the player
var rootItemParentId = _hashUtil.Generate();
var rootItemParentId = new MongoId();
// Update the delivery items to have the new root parent ID for root/orphaned items
package.Items = package.Items.AdoptOrphanedItems(rootItemParentId);
@@ -10,7 +10,6 @@ namespace SPTarkov.Server.Core.Callbacks;
[Injectable(TypePriority = OnUpdateOrder.DialogueCallbacks)]
public class DialogueCallbacks(
HashUtil _hashUtil,
TimeUtil _timeUtil,
HttpResponseUtil _httpResponseUtil,
DialogueController _dialogueController
@@ -47,7 +46,7 @@ public class DialogueCallbacks(
{
new()
{
Id = _hashUtil.Generate(),
Id = new Models.Common.MongoId(),
RegistrationId = 20,
DateTime = _timeUtil.GetTimeStamp(),
IsDeveloper = true,
@@ -200,15 +199,13 @@ public class DialogueCallbacks(
/// Handle client/mail/msg/send
/// </summary>
/// <returns></returns>
public virtual ValueTask<string> SendMessage(
public virtual async ValueTask<string> SendMessage(
string url,
SendMessageRequest request,
string sessionID
)
{
return new ValueTask<string>(
_httpResponseUtil.GetBody(_dialogueController.SendMessage(sessionID, request))
);
return _httpResponseUtil.GetBody(await _dialogueController.SendMessage(sessionID, request));
}
/// <summary>
@@ -6,7 +6,6 @@ using SPTarkov.Server.Core.Models.Eft.Insurance;
using SPTarkov.Server.Core.Models.Eft.ItemEvent;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks;
@@ -14,7 +13,6 @@ namespace SPTarkov.Server.Core.Callbacks;
[Injectable(TypePriority = OnUpdateOrder.InsuranceCallbacks)]
public class InsuranceCallbacks(
InsuranceController _insuranceController,
InsuranceService _insuranceService,
HttpResponseUtil _httpResponseUtil,
ConfigServer _configServer
) : IOnUpdate
@@ -17,7 +17,7 @@ public class SaveCallbacks(
public async Task OnLoad()
{
_backupService.StartBackupSystem();
await _backupService.StartBackupSystem();
await _saveServer.LoadAsync();
}
@@ -88,7 +88,7 @@ public class BotController(
bool ignoreRaidSettings = false
)
{
var difficulty = diffLevel.ToLower();
var difficulty = diffLevel.ToLowerInvariant();
var raidConfig = _profileActivityService
.GetProfileActivityRaidData(sessionId)
@@ -107,7 +107,7 @@ public class BotController(
// Check value chosen in pre-raid difficulty dropdown
// If value is not 'asonline', change requested difficulty to be what was chosen in dropdown
var botDifficultyDropDownValue =
raidConfig?.WavesSettings?.BotDifficulty?.ToString().ToLower() ?? "asonline";
raidConfig?.WavesSettings?.BotDifficulty?.ToString().ToLowerInvariant() ?? "asonline";
if (botDifficultyDropDownValue != "asonline")
{
difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(
@@ -138,8 +138,8 @@ public class BotController(
{
// If bot is usec/bear, swap to different name
var botTypeLower = botType.IsPmc()
? (botType.GetPmcSideByRole() ?? "usec").ToLower()
: botType.ToString().ToLower();
? (botType.GetPmcSideByRole() ?? "usec").ToLowerInvariant()
: botType.ToString().ToLowerInvariant();
// Get details from db
if (!botTypesDb.TryGetValue(botTypeLower, out var botDetails))
@@ -165,7 +165,7 @@ public class BotController(
continue;
}
var botNameKey = botType.ToString().ToLower();
var botNameKey = botType.ToString().ToLowerInvariant();
foreach (var (difficultyName, _) in botDetails.BotDifficulty)
{
// Bot doesn't exist in result, add
@@ -378,7 +378,7 @@ public class BotController(
protected MinMax<int> GetPmcLevelRangeForMap(string? location)
{
return _pmcConfig.LocationSpecificPmcLevelOverride!.GetValueOrDefault(
location?.ToLower() ?? "",
location?.ToLowerInvariant() ?? "",
null
);
}
@@ -430,7 +430,7 @@ public class BotController(
/// <returns>bot cap for map</returns>
public int GetBotCap(string location)
{
if (!_botConfig.MaxBotCap.TryGetValue(location.ToLower(), out var maxCap))
if (!_botConfig.MaxBotCap.TryGetValue(location.ToLowerInvariant(), out var maxCap))
{
return _botConfig.MaxBotCap["default"];
}
@@ -440,7 +440,7 @@ public class BotController(
_logger.Warning(
_serverLocalisationService.GetText(
"bot-no_bot_cap_found_for_location",
location.ToLower()
location.ToLowerInvariant()
)
);
}
@@ -538,15 +538,22 @@ public class DialogueController(
/// <param name="sessionId">Session/Player id</param>
/// <param name="request"></param>
/// <returns></returns>
public virtual string SendMessage(string sessionId, SendMessageRequest request)
public virtual async ValueTask<string> SendMessage(string sessionId, SendMessageRequest request)
{
_mailSendService.SendPlayerMessageToNpc(sessionId, request.DialogId!, request.Text!);
return (
_dialogueChatBots
.FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId)
?.HandleMessage(sessionId, request) ?? request.DialogId
) ?? string.Empty;
var chatBot = _dialogueChatBots.FirstOrDefault(cb =>
cb.GetChatBot().Id == request.DialogId
);
if (chatBot is not null)
{
return await chatBot.HandleMessage(sessionId, request);
}
else
{
return string.Empty;
}
}
/// <summary>
@@ -1,6 +1,7 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Game;
using SPTarkov.Server.Core.Models.Eft.Profile;
@@ -82,12 +83,12 @@ public class GameController(
return;
}
fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<string, int>(
new Dictionary<string, int>(),
fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<MongoId, int>(
new Dictionary<MongoId, int>(),
[]
);
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<string, int>(
new Dictionary<string, int>(),
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<MongoId, int>(
new Dictionary<MongoId, int>(),
[]
);
@@ -24,7 +24,6 @@ public class HealthController(
InventoryHelper _inventoryHelper,
ServerLocalisationService _serverLocalisationService,
HttpResponseUtil _httpResponseUtil,
HealthHelper _healthHelper,
ICloner _cloner
)
{
@@ -908,7 +908,7 @@ public class HideoutController(
var output = _eventOutputHolder.GetOutput(sessionID);
var hideoutDb = _databaseService.GetHideout();
if (request.RecipeId == HideoutHelper.BitcoinFarm)
if (request.RecipeId == HideoutHelper.BitcoinProductionId)
{
// Ensure server and client are in-sync when player presses 'get items' on farm
_hideoutHelper.UpdatePlayerHideout(sessionID);
@@ -1771,7 +1771,7 @@ public class HideoutController(
foreach (var poseKvP in request.Poses)
{
// Nullguard
pmcData.Hideout.MannequinPoses ??= new Dictionary<string, string>();
pmcData.Hideout.MannequinPoses ??= new Dictionary<string, MongoId>();
pmcData.Hideout.MannequinPoses[poseKvP.Key] = poseKvP.Value;
}
@@ -25,8 +25,6 @@ namespace SPTarkov.Server.Core.Controllers;
public class InsuranceController(
ISptLogger<InsuranceController> _logger,
RandomUtil _randomUtil,
MathUtil _mathUtil,
HashUtil _hashUtil,
TimeUtil _timeUtil,
EventOutputHolder _eventOutputHolder,
ItemHelper _itemHelper,
@@ -122,7 +120,7 @@ public class InsuranceController(
foreach (var insured in insuranceDetails)
{
// Create a new root parent ID for the message we'll be sending the player
var rootItemParentId = _hashUtil.Generate();
var rootItemParentId = new MongoId();
// Update the insured items to have the new root parent ID for root/orphaned items
insured.Items = insured.Items.AdoptOrphanedItems(rootItemParentId);
@@ -494,11 +492,11 @@ public class InsuranceController(
);
// Create prob array and add all attachments with rouble price as the weight
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(_cloner);
var attachmentsProbabilityArray = new ProbabilityObjectArray<MongoId, double?>(_cloner);
foreach (var (itemTpl, price) in weightedAttachmentByPrice)
{
attachmentsProbabilityArray.Add(
new ProbabilityObject<string, double?>(itemTpl, price, null)
new ProbabilityObject<MongoId, double?>(itemTpl, price, null)
);
}
@@ -527,9 +525,9 @@ public class InsuranceController(
/// <param name="attachments"></param>
/// <param name="attachmentPrices"></param>
protected void LogAttachmentsBeingRemoved(
List<string> attachmentIdsToRemove,
List<MongoId> attachmentIdsToRemove,
List<Item> attachments,
Dictionary<string, double> attachmentPrices
Dictionary<MongoId, double> attachmentPrices
)
{
var index = 1;
@@ -552,9 +550,9 @@ public class InsuranceController(
/// </summary>
/// <param name="attachments">Item attachments</param>
/// <returns></returns>
protected Dictionary<string, double> WeightAttachmentsByPrice(List<Item> attachments)
protected Dictionary<MongoId, double> WeightAttachmentsByPrice(List<Item> attachments)
{
var result = new Dictionary<string, double>();
var result = new Dictionary<MongoId, double>();
// Get a dictionary of item tpls + their rouble price
foreach (var attachment in attachments)
@@ -581,7 +579,7 @@ public class InsuranceController(
/// <param name="traderId">Trader the attachment is insured against</param>
/// <returns>Attachment count to remove</returns>
protected double GetAttachmentCountToRemove(
Dictionary<string, double> weightedAttachmentByPrice,
Dictionary<MongoId, double> weightedAttachmentByPrice,
string? traderId
)
{
@@ -872,7 +870,7 @@ public class InsuranceController(
{
var softInsertSlots = pmcData.Inventory.Items.Where(item =>
item.ParentId == itemWithSoftInserts.Id
&& _itemHelper.IsSoftInsertId(item.SlotId.ToLower())
&& _itemHelper.IsSoftInsertId(item.SlotId.ToLowerInvariant())
);
foreach (var softInsertSlot in softInsertSlots)
@@ -19,15 +19,11 @@ namespace SPTarkov.Server.Core.Controllers;
[Injectable]
public class InventoryController(
ISptLogger<InventoryController> _logger,
HashUtil _hashUtil,
RandomUtil _randomUtil,
HttpResponseUtil _httpResponseUtil,
PresetHelper _presetHelper,
InventoryHelper _inventoryHelper,
QuestHelper _questHelper,
HideoutHelper _hideoutHelper,
ProfileHelper _profileHelper,
PaymentHelper _paymentHelper,
TraderHelper _traderHelper,
ItemHelper _itemHelper,
DatabaseService _databaseService,
@@ -165,7 +161,7 @@ public class InventoryController(
if (itemToAdjust is null)
{
_logger.Error(
$"Unable find item: {request.Item} to: {request.State} on player {sessionId}to: "
$"Unable find item: {request.Item.Value.ToString()} to: {request.State} on player: {sessionId} to: "
);
return;
@@ -603,7 +599,7 @@ public class InventoryController(
/// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param>
/// <returns>Item tpl</returns>
protected string? GetExaminedItemTpl(InventoryExamineRequestData request, string? sessionId)
protected MongoId? GetExaminedItemTpl(InventoryExamineRequestData request, string? sessionId)
{
if (_presetHelper.IsPreset(request.Item))
{
@@ -1074,7 +1070,9 @@ public class InventoryController(
}
destinationItem.Upd.StackObjectsCount += sourceItem.Upd.StackObjectsCount; // Add source stackcount to destination
output.ProfileChanges[sessionID].Items.DeletedItems.Add(new Item { Id = sourceItem.Id }); // Inform client source item being deleted
output
.ProfileChanges[sessionID]
.Items.DeletedItems.Add(new DeletedItem { Id = sourceItem.Id }); // Inform client source item being deleted
var indexOfItemToRemove = inventoryItems.From.FindIndex(x => x.Id == sourceItem.Id);
if (indexOfItemToRemove == -1)
@@ -168,7 +168,7 @@ public class LauncherController(
var timeStampStr = Convert.ToString(timeStamp, 16).PadLeft(8, '0');
var counterStr = Convert.ToString(counter, 16).PadLeft(16, '0');
return timeStampStr.ToLower() + counterStr.ToLower();
return timeStampStr.ToLowerInvariant() + counterStr.ToLowerInvariant();
}
/// <summary>
@@ -196,7 +196,7 @@ public class LauncherV2Controller(
var timeStampStr = Convert.ToString(timeStamp, 16).PadLeft(8, '0');
var counterStr = Convert.ToString(counter, 16).PadLeft(16, '0');
return timeStampStr.ToLower() + counterStr.ToLower();
return timeStampStr.ToLowerInvariant() + counterStr.ToLowerInvariant();
}
protected string? GetSessionId(LoginRequestData info)
@@ -1,10 +1,10 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Location;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Cloners;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Controllers;
@@ -13,8 +13,7 @@ namespace SPTarkov.Server.Core.Controllers;
public class LocationController(
ISptLogger<LocationController> _logger,
DatabaseService _databaseService,
AirdropService _airdropService,
ICloner _cloner
AirdropService _airdropService
)
{
/// <summary>
@@ -29,7 +28,7 @@ public class LocationController(
var maps = locationsFromDb.GetDictionary();
// keyed by _id location property
var locationResult = new Dictionary<string, LocationBase>();
var locationResult = new Dictionary<MongoId, LocationBase>();
foreach (var (locationId, location) in maps)
{
@@ -47,7 +46,7 @@ public class LocationController(
// Clear out loot array
mapBase.Loot = [];
// Add map base data to dictionary
locationResult.Add(mapBase.IdField!, mapBase);
locationResult.Add(mapBase.IdField, mapBase);
}
return new LocationsGenerateAllResponse
@@ -5,7 +5,6 @@ using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Cloners;
using static SPTarkov.Server.Core.Services.MatchLocationService;
namespace SPTarkov.Server.Core.Controllers;
@@ -17,8 +16,7 @@ public class MatchController(
ConfigServer _configServer,
LocationLifecycleService _locationLifecycleService,
ProfileActivityService _profileActivityService,
WeatherHelper _weatherHelper,
ICloner _cloner
WeatherHelper _weatherHelper
)
{
protected readonly MatchConfig _matchConfig = _configServer.GetConfig<MatchConfig>();
@@ -1,5 +1,6 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Spt.Presets;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
@@ -19,7 +20,7 @@ public class PresetController(
public void Initialize()
{
var presets = _databaseService.GetGlobals().ItemPresets;
var result = new Dictionary<string, PresetCacheDetails>();
var result = new Dictionary<MongoId, PresetCacheDetails>();
foreach (var (presetId, preset) in presets)
{
if (presetId != preset.Id)
@@ -33,9 +34,9 @@ public class PresetController(
// Get root items tpl
var tpl = preset.Items.FirstOrDefault()?.Template;
result.TryAdd(tpl, new PresetCacheDetails { PresetIds = [] });
result.TryAdd(tpl.Value, new PresetCacheDetails { PresetIds = [] });
result.TryGetValue(tpl, out var details);
result.TryGetValue(tpl.Value, out var details);
details.PresetIds.Add(presetId);
if (preset.Encyclopedia is not null)
{
@@ -9,33 +9,17 @@ using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Launcher;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Routers;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Controllers;
[Injectable]
public class ProfileController(
ISptLogger<ProfileController> _logger,
HashUtil _hashUtil,
ICloner _cloner,
TimeUtil _timeUtil,
SaveServer _saveServer,
DatabaseService _databaseService,
ItemHelper _itemHelper,
ProfileFixerService _profileFixerService,
ServerLocalisationService _serverLocalisationService,
CreateProfileService _createProfileService,
SeasonalEventService _seasonalEventService,
PlayerScavGenerator _playerScavGenerator,
EventOutputHolder _eventOutputHolder,
TraderHelper _traderHelper,
DialogueHelper _dialogueHelper,
QuestHelper _questHelper,
QuestRewardHelper _questRewardHelper,
ProfileHelper _profileHelper
)
{
@@ -187,7 +171,7 @@ public class ProfileController(
var pmcData = _profileHelper.GetPmcProfile(sessionId);
pmcData.Info.Nickname = request.Nickname;
pmcData.Info.LowerNickname = request.Nickname.ToLower();
pmcData.Info.LowerNickname = request.Nickname.ToLowerInvariant();
}
return output;
@@ -223,7 +207,10 @@ public class ProfileController(
foreach (var profile in allProfiles)
{
var pmcProfile = profile?.CharacterData?.PmcData;
if (!pmcProfile?.Info?.LowerNickname?.Contains(request.Nickname.ToLower()) ?? false)
if (
!pmcProfile?.Info?.LowerNickname?.Contains(request.Nickname.ToLowerInvariant())
?? false
)
{
continue;
}
@@ -20,7 +20,6 @@ public class QuestController(
TimeUtil _timeUtil,
HttpResponseUtil _httpResponseUtil,
EventOutputHolder _eventOutputHolder,
ItemHelper _itemHelper,
MailSendService _mailSendService,
QuestHelper _questHelper,
QuestRewardHelper _questRewardHelper,
@@ -151,7 +150,7 @@ public class QuestController(
{
if (pmcData.TaskConditionCounters.TryGetValue(condition.Id, out _))
{
_logger.Error(
_logger.Warning(
$"Unable to add new task condition counter: {condition.ConditionType} for quest: {questId} to profile: {pmcData.SessionId} as it already exists"
);
}
@@ -320,7 +319,7 @@ public class QuestController(
// Important: don't tell the client to remove the attachments, it will handle it
output
.ProfileChanges[sessionID]
.Items.DeletedItems.Add(new Item { Id = itemHandover.Id });
.Items.DeletedItems.Add(new DeletedItem { Id = itemHandover.Id });
// Important: loop backward when removing items from the array we're looping on
while (index-- > 0)
@@ -1326,12 +1326,12 @@ public class RagfairController
/// Get prices for all items on flea
/// </summary>
/// <returns>Dictionary of tpl and item price</returns>
public Dictionary<string, double> GetAllFleaPrices()
public Dictionary<MongoId, double> GetAllFleaPrices()
{
return _ragfairPriceService.GetAllFleaPrices();
}
public Dictionary<string, double> GetStaticPrices()
public Dictionary<MongoId, double> GetStaticPrices()
{
return _ragfairPriceService.GetAllStaticPrices();
}
@@ -137,7 +137,7 @@ public class RepeatableQuestController(
}
// Subtype name of quest - daily/weekly/scav
var repeatableTypeLower = repeatablesOfTypeInProfile.Name.ToLower();
var repeatableTypeLower = repeatablesOfTypeInProfile.Name.ToLowerInvariant();
// Save for later standing loss calculation
var replacedQuestTraderId = questToReplace.TraderId;
@@ -575,7 +575,7 @@ public class RepeatableQuestController(
repeatableConfig,
pmcData
);
var repeatableTypeLower = repeatableConfig.Name.ToLower();
var repeatableTypeLower = repeatableConfig.Name.ToLowerInvariant();
var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData);
if (!canAccessRepeatables)
@@ -44,7 +44,7 @@ public class TraderController(
var traders = databaseService.GetTraders();
foreach (var (traderId, trader) in traders)
{
if (traderId == Traders.LIGHTHOUSEKEEPER || traderId == "ragfair")
if (traderId == Traders.LIGHTHOUSEKEEPER)
{
continue;
}
@@ -1,6 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Generators;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Weather;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
@@ -17,7 +16,6 @@ public class WeatherController(
WeatherGenerator _weatherGenerator,
SeasonalEventService _seasonalEventService,
RaidWeatherService _raidWeatherService,
WeatherHelper _weatherHelper,
ConfigServer _configServer
)
{
@@ -0,0 +1,265 @@
using SPTarkov.Server.Core.Models.Spt.Inventory;
namespace SPTarkov.Server.Core.Extensions
{
public static class ContainerExtensions
{
/// <summary>
/// Finds a slot for an item in a given 2D container map
/// </summary>
/// <param name="container2D">List of container with positions filled/free</param>
/// <param name="itemWidthX">Width of item</param>
/// <param name="itemHeightY">Height of item</param>
/// <returns>Location to place item in container</returns>
public static FindSlotResult FindSlotForItem(
this int[,] container2D,
int? itemWidthX,
int? itemHeightY
)
{
// Assume not rotated
var rotation = false;
// Find the min volume the item will take up
var minVolume = (itemWidthX < itemHeightY ? itemWidthX : itemHeightY) - 1;
var containerY = container2D.GetLength(0); // rows
var containerX = container2D.GetLength(1); // columns
var limitY = containerY - minVolume;
var limitX = containerX - minVolume;
// Every x+y slot taken up in container, exit
if (ContainerIsFull(container2D))
{
return new FindSlotResult(false);
}
// Down = y, iterate over rows
for (var row = 0; row < limitY; row++)
{
if (RowIsFull(container2D, row))
{
continue;
}
// Left to right across columns, look for free position
for (var column = 0; column < limitX; column++)
{
// Does item fit
if (
CanItemBePlacedInContainerAtPosition(
container2D,
row,
column,
itemWidthX.Value,
itemHeightY.Value
)
)
{
// Success, found a spot it fits
return new FindSlotResult(true, column, row, rotation);
}
if (!ItemBiggerThan1X1(itemWidthX.Value, itemHeightY.Value))
{
// Doesn't fit AND rotating won't help
continue;
}
// Rotate item by swapping x and y item values
if (
CanItemBePlacedInContainerAtPosition(
container2D,
row,
column,
itemHeightY.Value, // Swapped
itemWidthX.Value // Swapped
)
)
{
// Found a position for the item when rotated
rotation = true;
return new FindSlotResult(true, column, row, rotation);
}
}
}
// Tried all possible positions, nothing big enough for item
return new FindSlotResult(false);
}
/// <summary>
/// Find a free slot for an item to be placed at
/// </summary>
/// <param name="container2D">Container to place item in</param>
/// <param name="columnStartPositionX">Container y size</param>
/// <param name="rowStartPositionY">Container x size</param>
/// <param name="itemXWidth">Items width</param>
/// <param name="itemYHeight">Items height</param>
/// <param name="isRotated">is item rotated</param>
public static void FillContainerMapWithItem(
this int[,] container2D,
int columnStartPositionX,
int rowStartPositionY,
int? itemXWidth,
int? itemYHeight,
bool isRotated
)
{
var containerY = container2D.GetLength(0); // rows
var containerX = container2D.GetLength(1); // columns
// Swap height/width if item needs to be rotated to fit
var itemWidth = isRotated ? itemYHeight : itemXWidth;
var itemHeight = isRotated ? itemXWidth : itemYHeight;
var itemRowEndPosition = rowStartPositionY + (itemHeight - 1);
var itemColumnEndPosition = columnStartPositionX + (itemWidth - 1);
//Item is a 1x1, flag slot as taken and exit early
if (itemXWidth == 1 && itemYHeight == 1)
{
container2D[rowStartPositionY, columnStartPositionX] = 1;
return;
}
// Loop over rows and columns and flag each as taken by item
for (var y = rowStartPositionY; y <= itemRowEndPosition; y++)
{
for (var x = columnStartPositionX; x <= itemColumnEndPosition; x++)
{
if (container2D[y, x] == 0)
{
// Flag slot as used
container2D[y, x] = 1;
}
else
{
throw new Exception(
$"Slot at: ({containerX}, {containerY}) is already filled. Cannot fit: {itemXWidth} by {itemYHeight} item"
);
}
}
}
}
/// <summary>
/// Is the requested row full
/// </summary>
/// <param name="container2D">Container to check</param>
/// <param name="rowIndex">Index of row to check</param>
/// <returns>True = full</returns>
private static bool RowIsFull(int[,] container2D, int rowIndex)
{
var rowFull = true;
var containerColumnCount = container2D.GetLength(1); // Column
for (var col = 0; col < containerColumnCount; col++)
{
if (container2D[rowIndex, col] == 0)
{
rowFull = false;
break;
}
}
return rowFull;
}
/// <summary>
/// Is every slot in container full
/// </summary>
/// <param name="container2D">Container to check</param>
/// <returns>True = full</returns>
private static bool ContainerIsFull(int[,] container2D)
{
var containerY = container2D.GetLength(0); // rows
var containerX = container2D.GetLength(1); // columns
var containerFull = true;
for (var y = 0; y < containerY; y++)
{
for (var x = 0; x < containerX; x++)
{
if (container2D[y, x] == 0)
{
containerFull = false;
break;
}
}
if (!containerFull)
{
break;
}
}
return containerFull;
}
/// <summary>
/// Is the item size values passed in bigger than 1x1
/// </summary>
/// <param name="itemWidth">Width of item</param>
/// <param name="itemHeight">Height of item</param>
/// <returns>True = bigger than 1x1</returns>
private static bool ItemBiggerThan1X1(int itemWidth, int itemHeight)
{
return itemWidth + itemHeight > 2;
}
/// <summary>
/// Can an item of specified size be placed inside a 2d container at a specific position
/// </summary>
/// <param name="container">Container to find space in</param>
/// <param name="itemStartVerticalPos">Starting y position for item</param>
/// <param name="itemStartHorizontalPos">Starting x position for item</param>
/// <param name="itemWidth">Items width (y)</param>
/// <param name="itemHeight">Items height (x)</param>
/// <returns>True - slot found</returns>
public static bool CanItemBePlacedInContainerAtPosition(
this int[,] container,
int itemStartVerticalPos,
int itemStartHorizontalPos,
int itemWidth,
int itemHeight
)
{
var containerHeight = container.GetLength(0); // Rows
var containerWidth = container.GetLength(1); // Columns
var itemEndColPosition = itemStartHorizontalPos + itemWidth - 1;
var itemEndRowPosition = itemStartVerticalPos + itemHeight - 1;
// Check item isn't bigger than container when at position
if (itemEndColPosition > containerWidth - 1 || itemEndRowPosition > containerHeight - 1)
{
// Item is bigger than container, will never fit
return false;
}
// Early exit if exact spot is taken
if (container[itemStartVerticalPos, itemStartHorizontalPos] == 1)
{
return false;
}
// Single slot item, do direct check
if (itemWidth == 1 && itemHeight == 1)
{
return container[itemStartVerticalPos, itemStartHorizontalPos] == 0;
}
for (var row = itemStartVerticalPos; row <= itemEndRowPosition; row++)
{
for (var column = itemStartHorizontalPos; column <= itemEndColPosition; column++)
{
if (container[row, column] == 1)
{
// Occupied by something
return false;
}
}
}
return true; // Slot is free
}
}
}
@@ -158,7 +158,7 @@ namespace SPTarkov.Server.Core.Extensions
}
// Dev profile additions
if (fullProfile.ProfileInfo.Edition.ToLower().Contains("developer"))
if (fullProfile.ProfileInfo.Edition.ToLowerInvariant().Contains("developer"))
// CyberTark background
{
fullProfile.CustomisationUnlocks.Add(
@@ -185,7 +185,7 @@ namespace SPTarkov.Server.Core.Extensions
// Edge case - profile not created yet, fall back to what launcher has set
var launcherEdition = fullProfile.ProfileInfo.Edition;
switch (launcherEdition.ToLower())
switch (launcherEdition.ToLowerInvariant())
{
case "edge of darkness":
return GameEditions.EDGE_OF_DARKNESS;
@@ -215,5 +215,16 @@ namespace SPTarkov.Server.Core.Extensions
fullProfile.SptData.ExtraRepeatableQuests[repeatableId] += rewardValue;
}
}
/// <summary>
/// Is the provided session id for a developer account
/// </summary>
/// <param name="fullProfile">Profile to check</param>
/// <returns>True if account is developer</returns>
public static bool IsDeveloperAccount(this SptProfile fullProfile)
{
return fullProfile?.ProfileInfo?.Edition?.ToLowerInvariant().StartsWith("spt developer")
?? false;
}
}
}
@@ -1,4 +1,6 @@
using SPTarkov.Server.Core.Models.Common;
using System.Text.Json;
using SPTarkov.Common.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -284,16 +286,16 @@ namespace SPTarkov.Server.Core.Extensions
/// <returns>list of Item objects</returns>
public static List<Item> FindAndReturnChildrenAsItems(
this IEnumerable<Item> items,
string baseItemId,
MongoId baseItemId,
bool modsOnly = false
)
{
// Use dictionary to make key lookup faster, convert to list before being returned
OrderedDictionary<string, Item> result = [];
OrderedDictionary<MongoId, Item> result = [];
foreach (var childItem in items)
{
// Include itself
if (string.Equals(childItem.Id, baseItemId, StringComparison.Ordinal))
if (childItem.Id == baseItemId)
{
// Root item MUST be at 0 index for things like flea market offers
result.Insert(0, childItem.Id, childItem);
@@ -309,7 +311,8 @@ namespace SPTarkov.Server.Core.Extensions
// Items parentId matches root item AND returned items doesn't contain current child
if (
!result.ContainsKey(childItem.Id)
&& string.Equals(childItem.ParentId, baseItemId, StringComparison.Ordinal)
&& childItem.ParentId != "hideout"
&& childItem.ParentId == baseItemId
)
{
foreach (var item in FindAndReturnChildrenAsItems(items, childItem.Id))
@@ -342,5 +345,42 @@ namespace SPTarkov.Server.Core.Extensions
ExtensionData = item.ExtensionData,
};
}
public static ItemLocation? GetParsedLocation(this Item item)
{
if (item.Location is null)
{
return null;
}
if (item.Location is JsonElement element)
{
// TODO: when is this true
return element.ToObject<ItemLocation>();
}
return (ItemLocation)item.Location;
}
/// <summary>
/// Get a list of the item IDs (NOT tpls) inside a secure container
/// </summary>
/// <param name="items">Inventory items to look for secure container in</param>
/// <returns>List of ids</returns>
public static List<string> GetSecureContainerItems(this List<Item> items)
{
var secureContainer = items.First(x => x.SlotId == "SecuredContainer");
// No container found, drop out
if (secureContainer is null)
{
return [];
}
var itemsInSecureContainer = items.FindAndReturnChildrenByItems(secureContainer.Id);
// Return all items returned and exclude the secure container item itself
return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToList();
}
}
}
@@ -6,12 +6,6 @@ namespace SPTarkov.Server.Core.Extensions
{
public static class ProductionExtensions
{
private static readonly HashSet<string> _idCheck =
[
HideoutHelper.BitcoinFarm,
HideoutHelper.CultistCircleCraftId,
];
/// <summary>
/// Has the craft completed
/// Ignores bitcoin farm/cultist circle as they're continuous crafts
@@ -20,7 +14,9 @@ namespace SPTarkov.Server.Core.Extensions
/// <returns>True when craft is complete</returns>
public static bool IsCraftComplete(this Production craft)
{
return craft.Progress >= craft.ProductionTime && !_idCheck.Contains(craft.RecipeId);
return craft.Progress >= craft.ProductionTime
&& !craft.IsCraftOfType(HideoutAreas.BitcoinFarm)
&& !craft.IsCraftOfType(HideoutAreas.CircleOfCultists);
}
/// <summary>
@@ -34,9 +30,9 @@ namespace SPTarkov.Server.Core.Extensions
switch (hideoutType)
{
case HideoutAreas.WaterCollector:
return craft.RecipeId == HideoutHelper.WaterCollector;
return craft.RecipeId == HideoutHelper.WaterCollectorId;
case HideoutAreas.BitcoinFarm:
return craft.RecipeId == HideoutHelper.BitcoinFarm;
return craft.RecipeId == HideoutHelper.BitcoinProductionId;
case HideoutAreas.ScavCase:
return craft.SptIsScavCase ?? false;
case HideoutAreas.CircleOfCultists:
@@ -1,4 +1,5 @@
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
@@ -125,7 +126,7 @@ namespace SPTarkov.Server.Core.Extensions
return pmcData.IsParentInStash(itemToCheck.Id);
}
public static bool IsParentInStash(this PmcData pmcData, string itemId)
public static bool IsParentInStash(this PmcData pmcData, MongoId itemId)
{
// Item not found / has no parent
var item = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemId);
@@ -200,5 +201,45 @@ namespace SPTarkov.Server.Core.Extensions
return pmcData.Info.Level;
}
/// <summary>
/// Does the provided item have a root item with the provided id
/// </summary>
/// <param name="pmcData">Profile with items</param>
/// <param name="item">Item to check</param>
/// <param name="rootId">Root item id to check for</param>
/// <returns>True when item has rootId, false when not</returns>
public static bool DoesItemHaveRootId(this PmcData pmcData, Item item, string rootId)
{
var currentItem = item;
while (currentItem is not null)
{
// If we've found the equipment root ID, return true
if (currentItem.Id == rootId)
{
return true;
}
// Otherwise get the parent item
currentItem = pmcData.Inventory.Items.FirstOrDefault(item =>
item.Id == currentItem.ParentId
);
}
return false;
}
/// <summary>
/// Get status of a quest in player profile by its id
/// </summary>
/// <param name="pmcData">Profile to search</param>
/// <param name="questId">Quest id to look up</param>
/// <returns>QuestStatus enum</returns>
public static QuestStatusEnum GetQuestStatus(this PmcData pmcData, string questId)
{
var quest = pmcData.Quests?.FirstOrDefault(q => q.QId == questId);
return quest?.Status ?? QuestStatusEnum.Locked;
}
}
}
@@ -0,0 +1,39 @@
using System.Text;
namespace SPTarkov.Server.Core.Extensions
{
public static class StringExtensions
{
public static string Encode(this string value, EncodeType encode)
{
return encode switch
{
EncodeType.BASE64 => Convert.ToBase64String(Encoding.Default.GetBytes(value)),
EncodeType.HEX => Convert.ToHexString(Encoding.Default.GetBytes(value)),
EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)),
EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)),
_ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null),
};
}
public static string Decode(this string value, EncodeType encode)
{
return encode switch
{
EncodeType.BASE64 => Encoding.UTF8.GetString(Convert.FromBase64String(value)),
EncodeType.HEX => Encoding.UTF8.GetString(Convert.FromHexString(value)),
EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)),
EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)),
_ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null),
};
}
public enum EncodeType
{
BASE64,
HEX,
ASCII,
UTF8,
}
}
}
@@ -48,5 +48,20 @@ namespace SPTarkov.Server.Core.Extensions
{
return weaponTemplate.Properties.DefMagType;
}
/// <summary>
/// Get the default plate an armor has in its db item
/// </summary>
/// <param name="armorItem">Item to look up default plate</param>
/// <param name="modSlot">front/back</param>
/// <returns>Tpl of plate</returns>
public static string? GetDefaultPlateTpl(this TemplateItem armorItem, string modSlot)
{
var relatedItemDbModSlot = armorItem.Properties.Slots?.FirstOrDefault(slot =>
string.Equals(slot.Name, modSlot, StringComparison.OrdinalIgnoreCase)
);
return relatedItemDbModSlot?.Props?.Filters?.FirstOrDefault()?.Plate;
}
}
}
@@ -1,4 +1,5 @@
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
namespace SPTarkov.Server.Core.Extensions
{
@@ -14,7 +15,7 @@ namespace SPTarkov.Server.Core.Extensions
/// <returns>Modified assort</returns>
public static TraderAssort RemoveItemFromAssort(
this TraderAssort assort,
string itemId,
MongoId itemId,
bool isFlea = false
)
{
@@ -1,6 +1,7 @@
using System.Collections.Frozen;
using System.Globalization;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
@@ -83,6 +84,17 @@ public class BotEquipmentModGenerator(
"cartridges",
];
const string modRecieverKey = "mod_reciever";
const string modMount001Key = "mod_mount_001";
const string modGasBlockKey = "mod_gas_block";
const string modPistolGrip = "mod_pistol_grip";
const string modStockKey = "mod_stock";
const string modBarrelKey = "mod_barrel";
const string modHandguardKey = "mod_handguard";
const string modMountKey = "mod_mount";
const string modScopeKey = "mod_scope";
const string modScope000Key = "mod_scope_000";
protected readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
/// <summary>
@@ -118,7 +130,7 @@ public class BotEquipmentModGenerator(
foreach (var (modSlotName, modPool) in compatibleModsPool ?? [])
{
// Get the templates slot object from db
var itemSlotTemplate = GetModItemSlotFromDb(modSlotName, parentTemplate);
var itemSlotTemplate = GetModItemSlotFromDbTemplate(modSlotName, parentTemplate);
if (itemSlotTemplate is null)
{
_logger.Error(
@@ -174,12 +186,12 @@ public class BotEquipmentModGenerator(
// Slot can hold armor plates + we are filtering possible items by bot level, handle
if (
settings.BotEquipmentConfig.FilterPlatesByLevel.GetValueOrDefault(false)
&& _itemHelper.IsRemovablePlateSlot(modSlotName.ToLower())
&& _itemHelper.IsRemovablePlateSlot(modSlotName.ToLowerInvariant())
)
{
var plateSlotFilteringOutcome = FilterPlateModsForSlotByLevel(
settings,
modSlotName.ToLower(),
modSlotName.ToLowerInvariant(),
compatibleModsPool.GetValueOrDefault(modSlotName),
parentTemplate
);
@@ -206,7 +218,7 @@ public class BotEquipmentModGenerator(
}
// Choose random mod from pool and check its compatibility
string? modTpl = null;
MongoId? modTpl = null;
var found = false;
var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom);
while (exhaustableModPool.HasValues())
@@ -215,7 +227,7 @@ public class BotEquipmentModGenerator(
if (
modTpl is not null
&& !_botGeneratorHelper
.IsItemIncompatibleWithCurrentItems(equipment, modTpl, modSlotName)
.IsItemIncompatibleWithCurrentItems(equipment, modTpl.Value, modSlotName)
.Incompatible.GetValueOrDefault(false)
)
{
@@ -243,7 +255,7 @@ public class BotEquipmentModGenerator(
}
// Get chosen mods db template and check it fits into slot
var modTemplate = _itemHelper.GetItem(modTpl);
var modTemplate = _itemHelper.GetItem(modTpl.Value);
if (
!IsModValidForSlot(
modTemplate,
@@ -406,7 +418,7 @@ public class BotEquipmentModGenerator(
);
}
var defaultPlate = GetDefaultPlateTpl(armorItem, modSlot);
var defaultPlate = armorItem.GetDefaultPlateTpl(modSlot);
if (defaultPlate is not null)
{
// Return Default Plates cause couldn't get lowest level available from original selection
@@ -474,21 +486,6 @@ public class BotEquipmentModGenerator(
};
}
/// <summary>
/// Get the default plate an armor has in its db item
/// </summary>
/// <param name="armorItem">Item to look up default plate</param>
/// <param name="modSlot">front/back</param>
/// <returns>Tpl of plate</returns>
protected string? GetDefaultPlateTpl(TemplateItem armorItem, string modSlot)
{
var relatedItemDbModSlot = armorItem.Properties.Slots?.FirstOrDefault(slot =>
string.Equals(slot.Name, modSlot, StringComparison.OrdinalIgnoreCase)
);
return relatedItemDbModSlot?.Props?.Filters.FirstOrDefault()?.Plate;
}
/// <summary>
/// Get the matching armor slot from the default preset matching passed in armor tpl
/// </summary>
@@ -555,7 +552,7 @@ public class BotEquipmentModGenerator(
foreach (var modSlot in sortedModKeys)
{
// Check weapon has slot for mod to fit in
var modsParentSlot = GetModItemSlotFromDb(modSlot, request.ParentTemplate);
var modsParentSlot = GetModItemSlotFromDbTemplate(modSlot, request.ParentTemplate);
if (modsParentSlot is null)
{
_logger.Error(
@@ -634,12 +631,12 @@ public class BotEquipmentModGenerator(
continue;
}
var modToAddTemplate = modToAdd.Value;
var modToAddTemplate = modToAdd.Value.Value;
// Skip adding mod to weapon if type limit reached
if (
_botWeaponModLimitService.WeaponModHasReachedLimit(
request.BotData.EquipmentRole,
modToAddTemplate.Value,
modToAddTemplate,
request.ModLimits,
request.ParentTemplate,
request.Weapon
@@ -650,7 +647,7 @@ public class BotEquipmentModGenerator(
}
// If item is a mount for scopes, set scope chance to 100%, this helps fix empty mounts appearing on weapons
if (ModSlotCanHoldScope(modSlot, modToAddTemplate.Value.Parent))
if (ModSlotCanHoldScope(modSlot, modToAddTemplate.Parent))
{
// mod_mount was picked to be added to weapon, force scope chance to ensure its filled
List<string> scopeSlots =
@@ -669,7 +666,7 @@ public class BotEquipmentModGenerator(
{
AddCompatibleModsForProvidedMod(
"mod_scope",
modToAddTemplate.Value,
modToAddTemplate,
request.ModPool,
botEquipBlacklist
);
@@ -677,7 +674,7 @@ public class BotEquipmentModGenerator(
}
// If picked item is muzzle adapter that can hold a child, adjust spawn chance
if (ModSlotCanHoldMuzzleDevices(modSlot, modToAddTemplate.Value.Parent))
if (ModSlotCanHoldMuzzleDevices(modSlot, modToAddTemplate.Parent))
{
List<string> muzzleSlots = ["mod_muzzle", "mod_muzzle_000", "mod_muzzle_001"];
// Make chance of muzzle devices 95%, nearly certain but not guaranteed
@@ -685,7 +682,7 @@ public class BotEquipmentModGenerator(
}
// If front/rear sight are to be added, set opposite to 100% chance
if (ModIsFrontOrRearSight(modSlot, modToAddTemplate.Value.Id))
if (ModIsFrontOrRearSight(modSlot, modToAddTemplate.Id))
{
request.ModSpawnChances["mod_sight_front"] = 100;
request.ModSpawnChances["mod_sight_rear"] = 100;
@@ -695,7 +692,7 @@ public class BotEquipmentModGenerator(
// Force spawn chance to be 100% to ensure it gets added
if (
modSlot == "mod_handguard"
&& modToAddTemplate.Value.Properties.Slots.Any(slot => slot.Name == "mod_handguard")
&& modToAddTemplate.Properties.Slots.Any(slot => slot.Name == "mod_handguard")
&& !request.Weapon.Any(item => item.SlotId == "mod_launcher")
)
// Needed for handguards with lower
@@ -705,7 +702,7 @@ public class BotEquipmentModGenerator(
// If stock mod can take a sub stock mod, force spawn chance to be 100% to ensure sub-stock gets added
// Or if bot has stock force enabled
if (ShouldForceSubStockSlots(modSlot, botEquipConfig, modToAddTemplate.Value))
if (ShouldForceSubStockSlots(modSlot, botEquipConfig, modToAddTemplate))
{
// Stock mod can take additional stocks, could be a locking device, force 100% chance
List<string> subStockSlots =
@@ -719,7 +716,7 @@ public class BotEquipmentModGenerator(
}
// Gather stats on mods being added to weapon
if (_itemHelper.IsOfBaseclass(modToAddTemplate.Value.Id, BaseClasses.IRON_SIGHT))
if (_itemHelper.IsOfBaseclass(modToAddTemplate.Id, BaseClasses.IRON_SIGHT))
{
if (modSlot == "mod_sight_front")
{
@@ -732,7 +729,7 @@ public class BotEquipmentModGenerator(
}
else if (
!(request.WeaponStats.HasOptic ?? false)
&& _itemHelper.IsOfBaseclass(modToAddTemplate.Value.Id, BaseClasses.SIGHTS)
&& _itemHelper.IsOfBaseclass(modToAddTemplate.Id, BaseClasses.SIGHTS)
)
{
request.WeaponStats.HasOptic = true;
@@ -742,16 +739,16 @@ public class BotEquipmentModGenerator(
request.Weapon.Add(
CreateModItem(
modId,
modToAddTemplate.Value.Id,
modToAddTemplate.Id,
request.WeaponId,
modSlot,
modToAddTemplate.Value,
modToAddTemplate,
request.BotData.Role
)
);
// Update conflicting item list now item has been chosen
foreach (var conflictingItem in modToAddTemplate.Value.Properties.ConflictingItems)
foreach (var conflictingItem in modToAddTemplate.Properties.ConflictingItems)
{
request.ConflictingItemTpls.Add(conflictingItem);
}
@@ -760,30 +757,30 @@ public class BotEquipmentModGenerator(
// However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config
// where we decided to keep cartridges instead of camoras. And since a CylinderMagazine only has one cartridge entry and
// this entry is not to be filled, we need a special handling for the CylinderMagazine
var modParentItem = _itemHelper.GetItem(modToAddTemplate.Value.Parent).Value;
var modParentItem = _itemHelper.GetItem(modToAddTemplate.Parent).Value;
if (_botWeaponGeneratorHelper.MagazineIsCylinderRelated(modParentItem.Name))
{
// We don't have child mods, we need to create the camoras for the magazines instead
FillCamora(request.Weapon, request.ModPool, modId, modToAddTemplate.Value);
FillCamora(request.Weapon, request.ModPool, modId, modToAddTemplate);
}
else
{
var containsModInPool = request.ModPool.ContainsKey(modToAddTemplate.Value.Id);
var containsModInPool = request.ModPool.ContainsKey(modToAddTemplate.Id);
// Sometimes randomised slots are missing sub-mods, if so, get values from mod pool service
// Check for a randomisable slot + without data in modPool + item being added as additional slots
if (
isRandomisableSlot
&& !containsModInPool
&& modToAddTemplate.Value.Properties.Slots.Any()
&& modToAddTemplate.Properties.Slots.Any()
)
{
var modFromService = _botEquipmentModPoolService.GetModsForWeaponSlot(
modToAddTemplate.Value.Id
modToAddTemplate.Id
);
if (modFromService?.Count > 0)
{
request.ModPool[modToAddTemplate.Value.Id] = modFromService.ToDictionary();
request.ModPool[modToAddTemplate.Id] = modFromService.ToDictionary();
containsModInPool = true;
}
}
@@ -793,11 +790,11 @@ public class BotEquipmentModGenerator(
{
// Check for required mods the item we've added needs to be classified as 'valid'
var modFromService = _botEquipmentModPoolService.GetRequiredModsForWeaponSlot(
modToAddTemplate.Value.Id
modToAddTemplate.Id
);
if (modFromService?.Count > 0)
{
request.ModPool[modToAddTemplate.Value.Id] = modFromService;
request.ModPool[modToAddTemplate.Id] = modFromService;
containsModInPool = true;
}
}
@@ -809,7 +806,7 @@ public class BotEquipmentModGenerator(
Weapon = request.Weapon,
ModPool = request.ModPool,
WeaponId = modId,
ParentTemplate = modToAddTemplate.Value,
ParentTemplate = modToAddTemplate,
ModSpawnChances = request.ModSpawnChances,
AmmoTpl = request.AmmoTpl,
BotData = new BotData
@@ -869,7 +866,7 @@ public class BotEquipmentModGenerator(
/// <param name="modSlot">Slot to check</param>
/// <param name="tpl"></param>
/// <returns>true if it's a front/rear sight</returns>
public bool ModIsFrontOrRearSight(string modSlot, string tpl)
public bool ModIsFrontOrRearSight(string modSlot, MongoId tpl)
{
// Gas block /w front sight is special case, deem it a 'front sight' too
if (modSlot == "mod_gas_block" && tpl == "5ae30e795acfc408fb139a0b")
@@ -889,7 +886,7 @@ public class BotEquipmentModGenerator(
/// <returns>true if it can hold a scope</returns>
public bool ModSlotCanHoldScope(string modSlot, string modsParentId)
{
return _scopeIds.Contains(modSlot.ToLower()) && modsParentId == BaseClasses.MOUNT;
return _scopeIds.Contains(modSlot.ToLowerInvariant()) && modsParentId == BaseClasses.MOUNT;
}
/// <summary>
@@ -932,7 +929,7 @@ public class BotEquipmentModGenerator(
/// <returns>True if modSlot can have muzzle-related items</returns>
public bool ModSlotCanHoldMuzzleDevices(string modSlot, string? modsParentId)
{
return _muzzleIds.Contains(modSlot.ToLower());
return _muzzleIds.Contains(modSlot.ToLowerInvariant());
}
/// <summary>
@@ -943,7 +940,7 @@ public class BotEquipmentModGenerator(
/// <returns>Sorted array</returns>
public HashSet<string> SortModKeys(
HashSet<string> unsortedSlotKeys,
string itemTplWithKeysToSort
MongoId itemTplWithKeysToSort
)
{
// No need to sort with only 1 item in array
@@ -955,16 +952,6 @@ public class BotEquipmentModGenerator(
var isMount = _itemHelper.IsOfBaseclass(itemTplWithKeysToSort, BaseClasses.MOUNT);
HashSet<string> sortedKeys = [];
const string modRecieverKey = "mod_reciever";
const string modMount001Key = "mod_mount_001";
const string modGasBlockKey = "mod_gas_block";
const string modPistolGrip = "mod_pistol_grip";
const string modStockKey = "mod_stock";
const string modBarrelKey = "mod_barrel";
const string modHandguardKey = "mod_handguard";
const string modMountKey = "mod_mount";
const string modScopeKey = "mod_scope";
const string modScope000Key = "mod_scope_000";
// Mounts are a special case, they need scopes first before more mounts
if (isMount)
@@ -1055,9 +1042,9 @@ public class BotEquipmentModGenerator(
/// <param name="modSlot">e.g patron_in_weapon</param>
/// <param name="parentTemplate">item template</param>
/// <returns>Slot item</returns>
public Slot? GetModItemSlotFromDb(string modSlot, TemplateItem parentTemplate)
public Slot? GetModItemSlotFromDbTemplate(string modSlot, TemplateItem parentTemplate)
{
var modSlotLower = modSlot.ToLower();
var modSlotLower = modSlot.ToLowerInvariant();
switch (modSlotLower)
{
case "patron_in_weapon":
@@ -1100,7 +1087,7 @@ public class BotEquipmentModGenerator(
}
var spawnMod = _randomUtil.RollChance(
modSpawnChances.GetValueOrDefault(modSlotName.ToLower())
modSpawnChances.GetValueOrDefault(modSlotName.ToLowerInvariant())
);
if (
!spawnMod
@@ -1317,7 +1304,7 @@ public class BotEquipmentModGenerator(
/// <param name="request"></param>
/// <param name="modPool">Pool of mods that can be picked from</param>
/// <param name="parentSlot">Slot the picked mod will have as a parent</param>
/// <param name="choiceTypeEnum">How should chosen tpl be treated: DEFAULT_MOD/SPAWN/SKIP</param>
/// <param name="choiceTypeEnum">How should the chosen tpl be handled: DEFAULT_MOD/SPAWN/SKIP</param>
/// <param name="weapon">Array of weapon items chosen item will be added to</param>
/// <param name="modSlotName">Name of slot picked mod will be placed into</param>
/// <returns>Chosen weapon details</returns>
@@ -1556,8 +1543,8 @@ public class BotEquipmentModGenerator(
var parentSlotCompatibleItems = request
.ParentTemplate.Properties.Slots?.FirstOrDefault(slot =>
string.Equals(
slot.Name.ToLower(),
request.ModSlot.ToLower(),
slot.Name.ToLowerInvariant(),
request.ModSlot.ToLowerInvariant(),
StringComparison.Ordinal
)
)
@@ -1719,7 +1706,7 @@ public class BotEquipmentModGenerator(
/// <param name="modSlot">Slot to get mod to fill</param>
/// <param name="items">Items to ensure picked mod is compatible with</param>
/// <returns>Item tpl</returns>
public string? GetRandomModTplFromItemDb(
public MongoId? GetRandomModTplFromItemDb(
string fallbackModTpl,
Slot parentSlot,
string modSlot,
@@ -1856,7 +1843,7 @@ public class BotEquipmentModGenerator(
modPool.TryAdd(modTemplate.Id, new Dictionary<string, HashSet<MongoId>>());
modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubModsSet;
modPool[modTemplate.Id][desiredSlotObject.Name] = filteredMods;
}
/// <summary>
@@ -2064,7 +2051,7 @@ public class BotEquipmentModGenerator(
public HashSet<MongoId> FilterSightsByWeaponType(
Item weapon,
HashSet<MongoId> scopes,
Dictionary<string, List<string>> botWeaponSightWhitelist
Dictionary<MongoId, List<MongoId>> botWeaponSightWhitelist
)
{
var weaponDetails = _itemHelper.GetItem(weapon.Template);
@@ -188,7 +188,7 @@ public class BotGenerator(
BotGenerationDetails botGenerationDetails
)
{
var botRoleLowercase = botGenerationDetails.Role.ToLower();
var botRoleLowercase = botGenerationDetails.Role.ToLowerInvariant();
var botLevel = _botLevelGenerator.GenerateBotLevel(
botJsonTemplate.BotExperience.Level,
botGenerationDetails,
@@ -215,7 +215,7 @@ public class BotGenerator(
// Only Pmcs should have a lower nickname
bot.Info.LowerNickname = botGenerationDetails.IsPmc.GetValueOrDefault(false)
? bot.Info.Nickname.ToLower()
? bot.Info.Nickname.ToLowerInvariant()
: string.Empty;
// Only run when generating a 'fake' playerscav, not actual player scav
@@ -351,7 +351,7 @@ public class BotGenerator(
string role
)
{
if (!experiences.TryGetValue(botDifficulty.ToLower(), out var result))
if (!experiences.TryGetValue(botDifficulty.ToLowerInvariant(), out var result))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
@@ -386,7 +386,7 @@ public class BotGenerator(
string role
)
{
if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
if (!standingsForKill.TryGetValue(botDifficulty.ToLowerInvariant(), out var result))
{
_logger.Warning(
$"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`"
@@ -411,7 +411,7 @@ public class BotGenerator(
string role
)
{
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLowerInvariant(), out var result))
{
_logger.Warning(
$"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`"
@@ -474,7 +474,7 @@ public class BotGenerator(
/// <param name="botInventory">Bot to filter</param>
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
{
var containersToProcess = new List<Dictionary<string, double>>
var containersToProcess = new List<Dictionary<MongoId, double>>
{
botInventory.Items.Backpack,
botInventory.Items.Pockets,
@@ -645,7 +645,7 @@ public class BotInventoryGenerator(
/// <param name="equipmentBlacklist">Blacklist to filter mod pool with</param>
/// <returns>Filtered pool of mods</returns>
public Dictionary<string, HashSet<MongoId>> GetFilteredDynamicModsForItem(
string itemTpl,
MongoId itemTpl,
Dictionary<string, HashSet<MongoId>> equipmentBlacklist
)
{
@@ -13,7 +13,6 @@ namespace SPTarkov.Server.Core.Generators;
public class BotLevelGenerator(
ISptLogger<BotLevelGenerator> _logger,
RandomUtil _randomUtil,
MathUtil _mathUtil,
DatabaseService _databaseService
)
{
@@ -115,7 +115,7 @@ public class BotLootGenerator(
var grenadeCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Grenades.Weights);
// If bot has been flagged as not having loot, set below counts to 0
if (_botConfig.DisableLootOnBotTypes.Contains(botRole.ToLower()))
if (_botConfig.DisableLootOnBotTypes.Contains(botRole.ToLowerInvariant()))
{
backpackLootCount = 0;
pocketLootCount = 0;
@@ -458,7 +458,7 @@ public class BotLootGenerator(
{
// surv12
AddLootFromPool(
new Dictionary<string, double> { { "5d02797c86f774203f38e30a", 1 } },
new Dictionary<MongoId, double> { { "5d02797c86f774203f38e30a", 1 } },
[EquipmentSlots.SecuredContainer],
1,
botInventory,
@@ -470,7 +470,7 @@ public class BotLootGenerator(
// AFAK
AddLootFromPool(
new Dictionary<string, double> { { "60098ad7c2240c0fe85c570a", 1 } },
new Dictionary<MongoId, double> { { "60098ad7c2240c0fe85c570a", 1 } },
[EquipmentSlots.SecuredContainer],
10,
botInventory,
@@ -494,7 +494,7 @@ public class BotLootGenerator(
/// <param name="totalValueLimitRub">Total value of loot allowed in roubles</param>
/// <param name="isPmc">Is bot being generated for a pmc</param>
protected void AddLootFromPool(
Dictionary<string, double> pool,
Dictionary<MongoId, double> pool,
HashSet<EquipmentSlots> equipmentSlots,
double totalItemCount,
BotBaseInventory inventoryToAddItemsTo,
@@ -606,7 +606,7 @@ public class BotLootGenerator(
var itemAddedResult = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
equipmentSlots,
newRootItemId,
itemToAddTemplate?.Id,
itemToAddTemplate.Id,
itemWithChildrenToAdd,
inventoryToAddItemsTo,
containersIdFull
@@ -945,9 +945,9 @@ public class BotLootGenerator(
return _botConfig.ItemSpawnLimits["pmc"];
}
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLower()))
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLowerInvariant()))
{
return _botConfig.ItemSpawnLimits[botRole.ToLower()];
return _botConfig.ItemSpawnLimits[botRole.ToLowerInvariant()];
}
_logger.Warning(
@@ -545,7 +545,7 @@ public class BotWeaponGenerator(
GenerationData ubglMinMax = new()
{
Weights = new Dictionary<double, double> { { 1, 1 }, { 2, 1 } },
Whitelist = new Dictionary<string, double>(),
Whitelist = new Dictionary<MongoId, double>(),
};
// get ammo template from db
@@ -794,6 +794,7 @@ public class BotWeaponGenerator(
var magazineTemplate = _itemHelper.GetItem(
magazineSlot.Props?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault()
?? new MongoId(null)
);
if (!magazineTemplate.Key)
{
@@ -21,10 +21,8 @@ namespace SPTarkov.Server.Core.Generators;
public class LocationLootGenerator(
ISptLogger<LocationLootGenerator> _logger,
RandomUtil _randomUtil,
HashUtil _hashUtil,
ItemHelper _itemHelper,
DatabaseService _databaseService,
ContainerHelper _containerHelper,
PresetHelper _presetHelper,
ServerLocalisationService _serverLocalisationService,
SeasonalEventService _seasonalEventService,
@@ -60,7 +58,7 @@ public class LocationLootGenerator(
// Pull location-specific spawn limits from db
var itemsWithSpawnCountLimitsClone = _cloner.Clone(
_locationConfig.LootMaxSpawnLimits.GetValueOrDefault(locationId.ToLower())
_locationConfig.LootMaxSpawnLimits.GetValueOrDefault(locationId.ToLowerInvariant())
);
// Store items with spawn count limits inside so they can be accessed later inside static/dynamic loot spawn methods
@@ -70,13 +68,13 @@ public class LocationLootGenerator(
}
// Create containers with loot
result.AddRange(GenerateStaticContainers(locationId.ToLower(), staticAmmoDist));
result.AddRange(GenerateStaticContainers(locationId.ToLowerInvariant(), staticAmmoDist));
// Add dynamic loot to output loot
var dynamicSpawnPoints = GenerateDynamicLoot(
_cloner.Clone(locationDetails.LooseLoot.Value),
staticAmmoDist,
locationId.ToLower()
locationId.ToLowerInvariant()
);
// Merge dynamic spawns into result
@@ -542,7 +540,7 @@ public class LocationLootGenerator(
var containerTpl = containerClone.Template.Items.FirstOrDefault().Template;
// Create new unique parent id to prevent any collisions
var parentId = _hashUtil.Generate();
var parentId = new MongoId();
containerClone.Template.Root = parentId;
containerClone.Template.Items.FirstOrDefault().Id = parentId;
@@ -600,8 +598,7 @@ public class LocationLootGenerator(
: chosenItemWithChildren.Items;
// look for open slot to put chosen item into
var result = _containerHelper.FindSlotForItem(
containerMap,
var result = containerMap.FindSlotForItem(
chosenItemWithChildren.Width,
chosenItemWithChildren.Height
);
@@ -620,8 +617,7 @@ public class LocationLootGenerator(
}
// Find somewhere for item inside container
_containerHelper.FillContainerMapWithItem(
containerMap,
containerMap.FillContainerMapWithItem(
result.X.Value,
result.Y.Value,
chosenItemWithChildren.Width,
@@ -700,7 +696,7 @@ public class LocationLootGenerator(
/// <param name="containerTypeId">Container to get possible loot for</param>
/// <param name="staticLootDist">staticLoot.json</param>
/// <returns>ProbabilityObjectArray of item tpls + probability</returns>
protected ProbabilityObjectArray<string, float?> GetPossibleLootItemsForContainer(
protected ProbabilityObjectArray<MongoId, float?> GetPossibleLootItemsForContainer(
string containerTypeId,
Dictionary<string, StaticLootDetails> staticLootDist
)
@@ -708,7 +704,7 @@ public class LocationLootGenerator(
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
var itemDistribution = new ProbabilityObjectArray<string, float?>(_cloner);
var itemDistribution = new ProbabilityObjectArray<MongoId, float?>(_cloner);
var itemContainerDistribution = staticLootDist[containerTypeId]?.ItemDistribution;
if (itemContainerDistribution is null)
@@ -738,7 +734,7 @@ public class LocationLootGenerator(
}
itemDistribution.Add(
new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null)
new ProbabilityObject<MongoId, float?>(icd.Tpl, icd.RelativeProbability.Value, null)
);
}
@@ -180,7 +180,7 @@ public class LootGenerator(
/// </summary>
/// <param name="forcedLootToAdd">Dictionary of item tpls with minmax values</param>
/// <returns>Array of Item</returns>
public List<List<Item>> CreateForcedLoot(Dictionary<string, MinMax<int>> forcedLootToAdd)
public List<List<Item>> CreateForcedLoot(Dictionary<MongoId, MinMax<int>> forcedLootToAdd)
{
var result = new List<List<Item>>();
@@ -25,9 +25,9 @@ public class PMCLootGenerator(
private readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
// Store loot against its type, usec/bear
private readonly Dictionary<string, Dictionary<string, double>>? _backpackLootPool = [];
private readonly Dictionary<string, Dictionary<string, double>>? _pocketLootPool = [];
private readonly Dictionary<string, Dictionary<string, double>>? _vestLootPool = [];
private readonly Dictionary<string, Dictionary<MongoId, double>>? _backpackLootPool = [];
private readonly Dictionary<string, Dictionary<MongoId, double>>? _pocketLootPool = [];
private readonly Dictionary<string, Dictionary<MongoId, double>>? _vestLootPool = [];
protected readonly Lock BackpackLock = new();
protected readonly Lock PocketLock = new();
@@ -38,7 +38,7 @@ public class PMCLootGenerator(
/// </summary>
/// <param name="pmcRole">Role of PMC having loot generated (bear or usec)</param>
/// <returns>Dictionary of string and number</returns>
public Dictionary<string, double> GeneratePMCPocketLootPool(string pmcRole)
public Dictionary<MongoId, double> GeneratePMCPocketLootPool(string pmcRole)
{
lock (PocketLock)
{
@@ -72,7 +72,7 @@ public class PMCLootGenerator(
/// </summary>
/// <param name="pmcRole">Role of PMC having loot generated (bear or usec)</param>
/// <returns>Dictionary item template ids and a weighted chance of being picked</returns>
public Dictionary<string, double> GeneratePMCVestLootPool(string pmcRole)
public Dictionary<MongoId, double> GeneratePMCVestLootPool(string pmcRole)
{
lock (VestLock)
{
@@ -107,7 +107,7 @@ public class PMCLootGenerator(
/// </summary>
/// <param name="pmcRole">Role of PMC having loot generated (bear or usec)</param>
/// <returns>Dictionary of string and number</returns>
public Dictionary<string, double> GeneratePMCBackpackLootPool(string pmcRole)
public Dictionary<MongoId, double> GeneratePMCBackpackLootPool(string pmcRole)
{
lock (BackpackLock)
{
@@ -137,14 +137,14 @@ public class PMCLootGenerator(
/// <param name="itemTplAndParentBlacklist">Item and parent blacklist</param>
/// <param name="genericItemCheck">An optional delegate to validate the TemplateItem object being processed</param>
/// <returns>Dictionary of items and weights inversely tied to the items price</returns>
protected Dictionary<string, double> GenerateLootPool(
protected Dictionary<MongoId, double> GenerateLootPool(
string pmcRole,
HashSet<MongoId> allowedItemTypeWhitelist,
HashSet<MongoId> itemTplAndParentBlacklist,
Func<TemplateItem, bool>? genericItemCheck
)
{
var lootPool = new Dictionary<string, double>();
var lootPool = new Dictionary<MongoId, double>();
var items = databaseService.GetItems();
// Grab price overrides if they exist for the pmcRole passed in
@@ -207,7 +207,7 @@ public class PMCLootGenerator(
/// </summary>
/// <param name="pmcRole">role of PMC to look up</param>
/// <returns>Dictionary of overrides</returns>
protected Dictionary<string, double>? GetPMCPriceOverrides(string pmcRole)
protected Dictionary<MongoId, double>? GetPMCPriceOverrides(string pmcRole)
{
var pmcType = string.Equals(pmcRole, "pmcbear", StringComparison.OrdinalIgnoreCase)
? "bear"
@@ -229,7 +229,10 @@ public class PMCLootGenerator(
/// <param name="tpl">Item tpl to get price of</param>
/// <param name="pmcPriceOverrides"></param>
/// <returns>Rouble price</returns>
protected double GetItemPrice(string tpl, Dictionary<string, double>? pmcPriceOverrides = null)
protected double GetItemPrice(
MongoId tpl,
Dictionary<MongoId, double>? pmcPriceOverrides = null
)
{
if (
pmcPriceOverrides is not null
@@ -80,7 +80,7 @@ public class PlayerScavGenerator(
var scavData = _botGenerator.GeneratePlayerScav(
sessionID,
playerScavKarmaSettings.BotTypeForLoot.ToLower(),
playerScavKarmaSettings.BotTypeForLoot.ToLowerInvariant(),
"easy",
baseBotNode,
pmcDataClone
@@ -113,12 +113,12 @@ public class PlayerScavGenerator(
scavData.Quests = existingScavDataClone.Quests ?? [];
scavData.TaskConditionCounters =
existingScavDataClone.TaskConditionCounters
?? new Dictionary<string, TaskConditionCounter>();
?? new Dictionary<MongoId, TaskConditionCounter>();
scavData.Notes = existingScavDataClone.Notes ?? new Notes { DataNotes = new List<Note>() };
scavData.WishList =
existingScavDataClone.WishList
?? new DictionaryOrList<string, int>(new Dictionary<string, int>(), new List<int>());
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new Dictionary<string, bool>();
?? new DictionaryOrList<MongoId, int>(new Dictionary<MongoId, int>(), []);
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new Dictionary<MongoId, bool>();
// Add additional items to player scav as loot
AddAdditionalLootToPlayerScavContainers(
@@ -58,7 +58,12 @@ public class PmcWaveGenerator(
/// <param name="location"> Location Object </param>
public void ApplyWaveChangesToMap(LocationBase location)
{
if (!_pmcConfig.CustomPmcWaves.TryGetValue(location.Id.ToLower(), out var pmcWavesToAdd))
if (
!_pmcConfig.CustomPmcWaves.TryGetValue(
location.Id.ToLowerInvariant(),
out var pmcWavesToAdd
)
)
{
return;
}
@@ -1,19 +1,18 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Generators;
[Injectable]
public class RagfairAssortGenerator(
HashUtil hashUtil,
ItemHelper itemHelper,
PresetHelper presetHelper,
SeasonalEventService seasonalEventService,
@@ -23,7 +22,7 @@ public class RagfairAssortGenerator(
{
protected readonly RagfairConfig RagfairConfig = configServer.GetConfig<RagfairConfig>();
protected readonly List<string> RagfairItemInvalidBaseTypes =
protected readonly List<MongoId> RagfairItemInvalidBaseTypes =
[
BaseClasses.LOOT_CONTAINER, // Safe, barrel cache etc
BaseClasses.STASH, // Player inventory stash
@@ -133,16 +132,16 @@ public class RagfairAssortGenerator(
/// <param name="tplId"> tplid to add to item </param>
/// <param name="id"> id to add to item </param>
/// <returns> Hydrated Item object </returns>
protected Item CreateRagfairAssortRootItem(string tplId, string? id = null)
protected Item CreateRagfairAssortRootItem(MongoId tplId, MongoId? id = null)
{
if (string.IsNullOrEmpty(id))
{
id = hashUtil.Generate();
id = new MongoId();
}
return new Item
{
Id = id,
Id = id.Value,
Template = tplId,
ParentId = "hideout",
SlotId = "hideout",
@@ -2,6 +2,7 @@ using System.Diagnostics;
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Ragfair;
using SPTarkov.Server.Core.Models.Enums;
@@ -148,7 +149,7 @@ public class RagfairOfferGenerator(
var offer = new RagfairOffer
{
Id = hashUtil.Generate(),
Id = new MongoId(),
InternalId = offerCounter,
User = CreateUserDataForFleaOffer(userId, ragfairServerHelper.IsTrader(userId)),
Root = rootItem.Id,
@@ -467,7 +468,7 @@ public class RagfairOfferGenerator(
clonedAssort[0].SlotId = null;
CreateSingleOfferForItem(
hashUtil.Generate(),
new MongoId(),
clonedAssort,
isPreset,
itemToSellDetails.Value,
@@ -494,7 +495,9 @@ public class RagfairOfferGenerator(
}
var plateSlots = presetWithChildren
.Where(item => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower()))
.Where(item =>
itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLowerInvariant())
)
.ToList();
if (plateSlots.Count == 0)
// Has no plate slots e.g. "front_plate", exit
@@ -506,7 +509,7 @@ public class RagfairOfferGenerator(
foreach (var plateSlot in plateSlots)
{
var plateDetails = itemHelper.GetItem(plateSlot.Template).Value;
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower()))
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLowerInvariant()))
{
continue;
}
@@ -630,7 +633,7 @@ public class RagfairOfferGenerator(
}
var offerItemPlatesToRemove = itemWithChildren.Where(item =>
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLower())
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLowerInvariant())
);
// Latest first, to ensure we don't move later items off by 1 each time we remove an item below it
@@ -783,7 +786,7 @@ public class RagfairOfferGenerator(
/// </summary>
/// <param name="tpl"> Item to look for matching condition object</param>
/// <returns> Condition ID </returns>
protected string? GetDynamicConditionIdForTpl(string tpl)
protected string? GetDynamicConditionIdForTpl(MongoId tpl)
{
// Get keys from condition config dictionary
var configConditions = ragfairConfig.Dynamic.Condition.Keys;
@@ -6,7 +6,6 @@ using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Spt.Repeatable;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Json;
@@ -21,23 +20,20 @@ public class CompletionQuestGenerator(
DatabaseService databaseService,
SeasonalEventService seasonalEventService,
ServerLocalisationService localisationService,
ConfigServer configServer,
RandomUtil randomUtil,
MathUtil mathUtil,
HashUtil hashUtil,
ItemHelper itemHelper
) : IRepeatableQuestGenerator
{
protected const int MaxRandomNumberAttempts = 6;
protected QuestConfig QuestConfig = configServer.GetConfig<QuestConfig>();
/// <summary>
/// Generates a valid Completion quest
/// </summary>
/// <param name="sessionId">session Id to generate the quest for</param>
/// <param name="pmcLevel">player's level for requested items and reward generation</param>
/// <param name="traderId">trader from which the quest will be provided</param>
/// <param name="questTypePool"></param>
/// <param name="repeatableConfig">
/// The configuration for the repeatably kind (daily, weekly) as configured in QuestConfig
/// for the requested quest
@@ -398,7 +394,7 @@ public class CompletionQuestGenerator(
/// <param name="completionConfig">Completion config from quest.json</param>
/// <returns>object of "Completion"-condition</returns>
protected QuestCondition GenerateCondition(
string itemTpl,
MongoId itemTpl,
double value,
Completion completionConfig
)
@@ -424,7 +420,7 @@ public class CompletionQuestGenerator(
return new QuestCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Index = 0,
ParentId = "",
DynamicLocale = true,
@@ -21,7 +21,6 @@ namespace SPTarkov.Server.Core.Generators.RepeatableQuestGeneration;
public class EliminationQuestGenerator(
ISptLogger<EliminationQuestGenerator> logger,
RandomUtil randomUtil,
HashUtil hashUtil,
MathUtil mathUtil,
RepeatableQuestHelper repeatableQuestHelper,
ItemHelper itemHelper,
@@ -277,7 +276,7 @@ public class EliminationQuestGenerator(
}
var availableForFinishCondition = quest.Conditions.AvailableForFinish![0];
availableForFinishCondition.Counter!.Id = hashUtil.Generate();
availableForFinishCondition.Counter!.Id = new MongoId();
availableForFinishCondition.Counter.Conditions = [];
// Only add specific location condition if specific map selected
@@ -299,7 +298,7 @@ public class EliminationQuestGenerator(
)
);
availableForFinishCondition.Value = desiredKillCount;
availableForFinishCondition.Id = hashUtil.Generate();
availableForFinishCondition.Id = new MongoId();
// Get the quest location, default to any if none exist
quest.Location = repeatableQuestHelper.GetQuestLocationByMapId(locationKey) ?? "any";
@@ -744,7 +743,7 @@ public class EliminationQuestGenerator(
{
return new QuestConditionCounterCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
DynamicLocale = true,
Target = new ListOrT<string>(location, null),
ConditionType = "Location",
@@ -770,7 +769,7 @@ public class EliminationQuestGenerator(
{
var killConditionProps = new QuestConditionCounterCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
DynamicLocale = true,
Target = new ListOrT<string>(null, target), // e,g, "AnyPmc"
Value = 1,
@@ -7,7 +7,6 @@ using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Spt.Repeatable;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Json;
@@ -21,10 +20,8 @@ public class ExplorationQuestGenerator(
RepeatableQuestRewardGenerator repeatableQuestRewardGenerator,
DatabaseService databaseService,
ServerLocalisationService localisationService,
ConfigServer configServer,
RandomUtil randomUtil,
MathUtil mathUtil,
HashUtil hashUtil
MathUtil mathUtil
) : IRepeatableQuestGenerator
{
protected record LocationInfo(
@@ -34,8 +31,6 @@ public class ExplorationQuestGenerator(
int NumOfExtractsRequired
);
protected QuestConfig QuestConfig = configServer.GetConfig<QuestConfig>();
/// <summary>
/// Generates a valid Exploration quest
/// </summary>
@@ -216,7 +211,7 @@ public class ExplorationQuestGenerator(
/// <returns>List of Exit objects</returns>
protected List<Exit>? GetLocationExitsForSide(string locationKey, PlayerGroup playerGroup)
{
var mapExtracts = databaseService.GetLocation(locationKey.ToLower())?.AllExtracts;
var mapExtracts = databaseService.GetLocation(locationKey.ToLowerInvariant())?.AllExtracts;
return mapExtracts?.Where(exit => exit.Side == Enum.GetName(playerGroup)).ToList();
}
@@ -254,7 +249,7 @@ public class ExplorationQuestGenerator(
var exitStatusCondition = new QuestConditionCounterCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
DynamicLocale = true,
Status = ["Survived"],
ConditionType = "ExitStatus",
@@ -262,20 +257,20 @@ public class ExplorationQuestGenerator(
var locationCondition = new QuestConditionCounterCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
DynamicLocale = true,
Target = new ListOrT<string>(locationInfo.LocationTarget, null),
ConditionType = "Location",
};
quest.Conditions.AvailableForFinish![0].Counter!.Id = hashUtil.Generate();
quest.Conditions.AvailableForFinish![0].Counter!.Id = new MongoId();
quest.Conditions.AvailableForFinish![0].Counter!.Conditions =
[
exitStatusCondition,
locationCondition,
];
quest.Conditions.AvailableForFinish[0].Value = locationInfo.NumOfExtractsRequired;
quest.Conditions.AvailableForFinish[0].Id = hashUtil.Generate();
quest.Conditions.AvailableForFinish[0].Id = new MongoId();
quest.Location = location;
@@ -357,7 +352,7 @@ public class ExplorationQuestGenerator(
{
return new QuestConditionCounterCondition
{
Id = hashUtil.Generate(),
Id = new MongoId(),
DynamicLocale = true,
ExitName = exit.Name,
ConditionType = "ExitName",
@@ -20,7 +20,6 @@ namespace SPTarkov.Server.Core.Generators.RepeatableQuestGeneration;
public class RepeatableQuestRewardGenerator(
ISptLogger<RepeatableQuestRewardGenerator> logger,
RandomUtil randomUtil,
HashUtil hashUtil,
MathUtil mathUtil,
DatabaseService databaseService,
ItemHelper itemHelper,
@@ -95,7 +94,7 @@ public class RepeatableQuestRewardGenerator(
rewards.Success.Add(
new Reward
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Unknown = false,
GameMode = [],
AvailableInGameEditions = [],
@@ -193,7 +192,7 @@ public class RepeatableQuestRewardGenerator(
{
Reward reward = new()
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Unknown = false,
GameMode = [],
AvailableInGameEditions = [],
@@ -219,7 +218,7 @@ public class RepeatableQuestRewardGenerator(
var targetSkill = randomUtil.GetArrayValue(eliminationConfig.PossibleSkillRewards);
Reward reward = new()
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Unknown = false,
GameMode = [],
AvailableInGameEditions = [],
@@ -657,17 +656,17 @@ public class RepeatableQuestRewardGenerator(
/// <param name="foundInRaid"> If generated Item is found in raid, default True </param>
/// <returns> Object of "Reward"-item-type </returns>
protected Reward GeneratePresetReward(
string tpl,
MongoId tpl,
int count,
int index,
List<Item>? preset,
bool foundInRaid = true
)
{
var id = hashUtil.Generate();
var id = new MongoId();
var questRewardItem = new Reward
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Unknown = false,
GameMode = [],
AvailableInGameEditions = [],
@@ -713,10 +712,10 @@ public class RepeatableQuestRewardGenerator(
bool foundInRaid = true
)
{
var id = hashUtil.Generate();
var id = new MongoId();
var questRewardItem = new Reward
{
Id = hashUtil.Generate(),
Id = new MongoId(),
Unknown = false,
GameMode = [],
AvailableInGameEditions = [],
@@ -819,7 +818,7 @@ public class RepeatableQuestRewardGenerator(
/// <param name="itemBaseWhitelist"> Default null, specific trader item base classes</param>
/// <returns> True if item is valid reward </returns>
public bool IsValidRewardItem(
string tpl,
MongoId tpl,
HashSet<MongoId> itemTplBlacklist,
HashSet<MongoId> itemTypeBlacklist,
List<MongoId>? itemBaseWhitelist = null
@@ -4,7 +4,6 @@ using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
namespace SPTarkov.Server.Core.Helpers;
@@ -12,10 +11,7 @@ namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class AssortHelper(
ISptLogger<AssortHelper> _logger,
ItemHelper _itemHelper,
DatabaseServer _databaseServer,
ServerLocalisationService _serverLocalisationService,
QuestHelper _questHelper
ServerLocalisationService _serverLocalisationService
)
{
/// <summary>
@@ -58,10 +54,7 @@ public class AssortHelper(
}
// Remove assort if quest in profile does not have status that unlocks assort
var questStatusInProfile = _questHelper.GetQuestStatus(
pmcProfile,
unlockValues.Value.Key
);
var questStatusInProfile = pmcProfile.GetQuestStatus(unlockValues.Value.Key);
if (!unlockValues.Value.Value.Contains(questStatusInProfile))
{
strippedTraderAssorts = traderAssorts.RemoveItemFromAssort(assortId.Key, isFlea);
@@ -37,8 +37,8 @@ public class BotDifficultyHelper(
)
{
var desiredType = _botHelper.IsBotPmc(type)
? _botHelper.GetPmcSideByRole(type).ToLower()
: type.ToLower();
? _botHelper.GetPmcSideByRole(type).ToLowerInvariant()
: type.ToLowerInvariant();
if (!botDb.Types.ContainsKey(desiredType))
{
// No bot found, get fallback difficulty values
@@ -85,7 +85,7 @@ public class BotDifficultyHelper(
StringComparison.OrdinalIgnoreCase
)
? difficulty
: _pmcConfig.Difficulty.ToLower();
: _pmcConfig.Difficulty.ToLowerInvariant();
difficultySetting = ConvertBotDifficultyDropdownToBotDifficulty(difficultySetting);
@@ -101,14 +101,14 @@ public class BotDifficultyHelper(
/// <returns>bot difficulty</returns>
public string ConvertBotDifficultyDropdownToBotDifficulty(string dropDownDifficulty)
{
switch (dropDownDifficulty.ToLower())
switch (dropDownDifficulty.ToLowerInvariant())
{
case "medium":
return "normal";
case "random":
return ChooseRandomDifficulty();
default:
return dropDownDifficulty.ToLower();
return dropDownDifficulty.ToLowerInvariant();
}
}
@@ -2,6 +2,7 @@ using System.Collections.Frozen;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Constants;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Bots;
@@ -21,7 +22,6 @@ public class BotGeneratorHelper(
DurabilityLimitsHelper _durabilityLimitsHelper,
ItemHelper _itemHelper,
InventoryHelper _inventoryHelper,
ContainerHelper _containerHelper,
ProfileActivityService _profileActivityService,
ServerLocalisationService _serverLocalisationService,
ConfigServer _configServer
@@ -37,7 +37,11 @@ public class BotGeneratorHelper(
EquipmentSlots.ArmBand.ToString(),
];
private static readonly string[] _pmcTypes = [Sides.PmcBear.ToLower(), Sides.PmcUsec.ToLower()];
private static readonly string[] _pmcTypes =
[
Sides.PmcBear.ToLowerInvariant(),
Sides.PmcUsec.ToLowerInvariant(),
];
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
@@ -382,7 +386,7 @@ public class BotGeneratorHelper(
/// <returns>false if no incompatibilities, also has incompatibility reason</returns>
public ChooseRandomCompatibleModResult IsItemIncompatibleWithCurrentItems(
List<Item> itemsEquipped,
string tplToCheck,
MongoId tplToCheck,
string equipmentSlot
)
{
@@ -596,15 +600,15 @@ public class BotGeneratorHelper(
/// </summary>
/// <param name="equipmentSlots">Slot to add item+children into</param>
/// <param name="rootItemId">Root item id to use as mod items parentId</param>
/// <param name="rootItemTplId">Root itms tpl id</param>
/// <param name="rootItemTplId">Root items tpl id</param>
/// <param name="itemWithChildren">Item to add</param>
/// <param name="inventory">Inventory to add item+children into</param>
/// <param name="containersIdFull"></param>
/// <returns>ItemAddedResult result object</returns>
public ItemAddedResult AddItemWithChildrenToEquipmentSlot(
HashSet<EquipmentSlots> equipmentSlots,
string rootItemId,
string? rootItemTplId,
MongoId rootItemId,
MongoId rootItemTplId,
List<Item> itemWithChildren,
BotBaseInventory inventory,
HashSet<string>? containersIdFull = null
@@ -665,7 +669,7 @@ public class BotGeneratorHelper(
}
// Get x/y grid size of item
var itemSize = _inventoryHelper.GetItemSize(
var (itemWidth, itemHeight) = _inventoryHelper.GetItemSize(
rootItemTplId,
rootItemId,
itemWithChildren
@@ -680,7 +684,7 @@ public class BotGeneratorHelper(
if (
slotGrid.Props?.CellsH == 0
|| slotGrid.Props?.CellsV == 0
|| itemSize[0] * itemSize[1] > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH
|| itemWidth * itemHeight > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH
)
{
continue;
@@ -718,11 +722,7 @@ public class BotGeneratorHelper(
);
// Try to fit item into grid
var findSlotResult = _containerHelper.FindSlotForItem(
slotGridMap,
itemSize[0],
itemSize[1]
);
var findSlotResult = slotGridMap.FindSlotForItem(itemWidth, itemHeight);
// Free slot found, add item
if (findSlotResult.Success ?? false)
@@ -768,7 +768,7 @@ public class BotGeneratorHelper(
}
// if the item was a one by one, we know it must be full. Or if the maps cant find a slot for a one by one
if (itemSize[0] == 1 && itemSize[1] == 1)
if (itemWidth == 1 && itemHeight == 1)
{
containersIdFull.Add(equipmentSlotId.ToString());
}
@@ -796,7 +796,7 @@ public class BotGeneratorHelper(
}
// Filter out all items without location prop, (child items)
var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null).ToList();
var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null);
foreach (var rootItem in containerRootItems)
{
// Check item in container for children, store for later insertion into `containerItemsToCheck`
@@ -817,7 +817,7 @@ public class BotGeneratorHelper(
/// <param name="slotGrid">Items sub-grid we want to place item inside</param>
/// <param name="itemTpl">Item tpl being placed</param>
/// <returns>True if allowed</returns>
protected bool ItemAllowedInContainer(Grid? slotGrid, string? itemTpl)
protected bool ItemAllowedInContainer(Grid? slotGrid, MongoId itemTpl)
{
var propFilters = slotGrid?.Props?.Filters;
var excludedFilter = propFilters?.FirstOrDefault()?.ExcludedFilter ?? [];
@@ -22,10 +22,10 @@ public class BotHelper(
{
private static readonly FrozenSet<string> _pmcTypeIds =
[
Sides.Usec.ToLower(),
Sides.Bear.ToLower(),
Sides.PmcBear.ToLower(),
Sides.PmcUsec.ToLower(),
Sides.Usec.ToLowerInvariant(),
Sides.Bear.ToLowerInvariant(),
Sides.PmcBear.ToLowerInvariant(),
Sides.PmcUsec.ToLowerInvariant(),
];
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
@@ -39,7 +39,7 @@ public class BotHelper(
/// <returns>BotType object</returns>
public BotType? GetBotTemplate(string role)
{
if (!_databaseService.GetBots().Types.TryGetValue(role?.ToLower(), out var bot))
if (!_databaseService.GetBots().Types.TryGetValue(role?.ToLowerInvariant(), out var bot))
{
_logger.Error($"Unable to get bot of type: {role} from DB");
@@ -56,7 +56,7 @@ public class BotHelper(
/// <returns>true if is pmc</returns>
public bool IsBotPmc(string? botRole)
{
return _pmcTypeIds.Contains(botRole?.ToLower());
return _pmcTypeIds.Contains(botRole?.ToLowerInvariant());
}
public bool IsBotBoss(string botRole)
@@ -135,10 +135,10 @@ public class BotHelper(
{
HashSet<string> listToCheck =
[
_pmcConfig.UsecType.ToLower(),
_pmcConfig.BearType.ToLower(),
_pmcConfig.UsecType.ToLowerInvariant(),
_pmcConfig.BearType.ToLowerInvariant(),
];
return listToCheck.Contains(botRole.ToLower());
return listToCheck.Contains(botRole.ToLowerInvariant());
}
/// <summary>
@@ -227,7 +227,7 @@ public class BotHelper(
)
{
_logger.Error($"Unknown faction: {chosenFaction} Defaulting to: {Sides.Usec}");
chosenFaction = Sides.Usec.ToLower();
chosenFaction = Sides.Usec.ToLowerInvariant();
chosenFactionDetails = _databaseService.GetBots().Types[chosenFaction];
}
@@ -34,10 +34,11 @@ public class BotWeaponGeneratorHelper(
double? chamberBulletCount = 0;
if (MagazineIsCylinderRelated(parentItem.Name))
{
var firstSlotAmmoTpl = magTemplate
.Properties.Cartridges.FirstOrDefault()
?.Props.Filters[0]
.Filter.FirstOrDefault();
var firstSlotAmmoTpl =
magTemplate
.Properties.Cartridges.FirstOrDefault()
?.Props.Filters[0]
.Filter.FirstOrDefault() ?? new MongoId(null);
var ammoMaxStackSize =
_itemHelper.GetItem(firstSlotAmmoTpl).Value?.Properties?.StackMaxSize ?? 1;
chamberBulletCount =
@@ -1,211 +0,0 @@
using System.Text.Json.Serialization;
using SPTarkov.DI.Annotations;
namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class ContainerHelper
{
/// <summary>
/// Finds a slot for an item in a given 2D container map
/// </summary>
/// <param name="container2D">List of container with positions filled/free</param>
/// <param name="itemX">Width of item</param>
/// <param name="itemY">Height of item</param>
/// <returns>Location to place item in container</returns>
public FindSlotResult FindSlotForItem(int[][] container2D, int? itemX, int? itemY)
{
// Assume not rotated
var rotation = false;
var minVolume = (itemX < itemY ? itemX : itemY) - 1;
var containerY = container2D.Length;
var containerX = container2D[0].Length;
var limitY = containerY - minVolume;
var limitX = containerX - minVolume;
// Every x+y slot taken up in container, exit
if (container2D.All(x => x.All(y => y == 1)))
{
return new FindSlotResult(false);
}
// Down = y
for (var y = 0; y < limitY; y++)
{
if (container2D[y].All(x => x == 1))
// Every item in row is full, skip row
{
continue;
}
// Go left to right across x-axis looking for free position
for (var x = 0; x < limitX; x++)
{
if (
CanItemBePlacedInContainerAtPosition(
container2D,
containerX,
containerY,
x,
y,
itemX!.Value,
itemY!.Value
)
)
{
// Success, return result
return new FindSlotResult(true, x, y, rotation);
}
if (ItemBiggerThan1X1(itemX!.Value, itemY!.Value))
{
// Pointless rotating a 1x1, try next position across
continue;
}
// Bigger than 1x1, try rotating by swapping x and y values
if (
!CanItemBePlacedInContainerAtPosition(
container2D,
containerX,
containerY,
x,
y,
itemY!.Value,
itemX!.Value
)
)
{
continue;
}
// Found a position for item when rotated
rotation = true;
return new FindSlotResult(true, x, y, rotation);
}
}
// Tried all possible positions, nothing big enough for item
return new FindSlotResult(false);
}
protected static bool ItemBiggerThan1X1(int itemWidth, int itemHeight)
{
return itemWidth + itemHeight > 2;
}
/// <summary>
/// Can an item of specified size be placed inside a 2d container at a specific position
/// </summary>
/// <param name="container">Container to find space in</param>
/// <param name="containerWidth">Container x size</param>
/// <param name="containerHeight">Container y size</param>
/// <param name="startXPos">Starting x position for item</param>
/// <param name="startYPos">Starting y position for item</param>
/// <param name="itemWidth">Items width</param>
/// <param name="itemHeight">Items height</param>
/// <returns>True - slot found</returns>
protected bool CanItemBePlacedInContainerAtPosition(
int[][] container,
int containerWidth,
int containerHeight,
int startXPos,
int startYPos,
int itemWidth,
int itemHeight
)
{
// Check item isn't bigger than container when at position
if (startXPos + itemWidth > containerWidth || startYPos + itemHeight > containerHeight)
{
return false;
}
// Check each position item will take up in container, go across and then down
for (var itemY = startYPos; itemY < startYPos + itemHeight; itemY++)
{
for (var itemX = startXPos; itemX < startXPos + itemWidth; itemX++)
{
// e,g for a 2x2 item; [0,0] then [0,1] then [1,0] then [1,1]
if (container[itemY][itemX] != 0)
{
// x,y Position blocked, can't place
return false;
}
}
}
return true;
}
/// <summary>
/// Find a free slot for an item to be placed at
/// </summary>
/// <param name="container2D">Container to place item in</param>
/// <param name="x">Container x size</param>
/// <param name="y">Container y size</param>
/// <param name="itemW">Items width</param>
/// <param name="itemH">Items height</param>
/// <param name="rotate">is item rotated</param>
public void FillContainerMapWithItem(
int[][] container2D,
int x,
int y,
int? itemW,
int? itemH,
bool rotate
)
{
// Swap height/width if we want to fit it in rotated
var itemWidth = rotate ? itemH : itemW;
var itemHeight = rotate ? itemW : itemH;
for (var tmpY = y; tmpY < y + itemHeight; tmpY++)
for (var tmpX = x; tmpX < x + itemWidth; tmpX++)
{
if (container2D[tmpY][tmpX] == 0)
// Flag slot as used
{
container2D[tmpY][tmpX] = 1;
}
else
{
throw new Exception(
$"Slot at({x}, {y}) is already filled. Cannot fit a {itemW} by {itemH} item"
);
}
}
}
}
public class FindSlotResult
{
public FindSlotResult(bool success)
{
Success = success;
}
public FindSlotResult(bool success, int x, int y, bool rotation)
{
Success = success;
X = x;
Y = y;
Rotation = rotation;
}
public FindSlotResult() { }
[JsonPropertyName("success")]
public bool? Success { get; set; }
[JsonPropertyName("x")]
public int? X { get; set; }
[JsonPropertyName("y")]
public int? Y { get; set; }
[JsonPropertyName("rotation")]
public bool? Rotation { get; set; }
}
@@ -1,18 +1,19 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
namespace SPTarkov.Server.Core.Helpers
{
[Injectable]
public class CounterTrackerHelper
{
private Dictionary<string, int> _maxCounts = new();
private readonly Dictionary<string, int> _trackedCounts = new();
private Dictionary<MongoId, int> _maxCounts = new();
private readonly Dictionary<MongoId, int> _trackedCounts = new();
/// <summary>
/// Add dictionary of keys and their matching limits to track
/// </summary>
/// <param name="maxCounts">Values to store</param>
public void AddDataToTrack(Dictionary<string, int> maxCounts)
public void AddDataToTrack(Dictionary<MongoId, int> maxCounts)
{
_maxCounts = maxCounts;
}
@@ -23,7 +24,7 @@ namespace SPTarkov.Server.Core.Helpers
/// <param name="key"></param>
/// <param name="countToIncrementBy"></param>
/// <returns>True = above max count</returns>
public bool IncrementCount(string key, int countToIncrementBy = 1)
public bool IncrementCount(MongoId key, int countToIncrementBy = 1)
{
// Not tracked, skip
if (!_maxCounts.Any() || !_maxCounts.ContainsKey(key))
@@ -3,7 +3,6 @@ using SPTarkov.Server.Core.Models.Eft.Dialog;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Callbacks;
namespace SPTarkov.Server.Core.Helpers.Dialogue;
@@ -20,7 +19,7 @@ public abstract class AbstractDialogChatBot(
public abstract UserDialogInfo GetChatBot();
public string? HandleMessage(string sessionId, SendMessageRequest request)
public async ValueTask<string> HandleMessage(string sessionId, SendMessageRequest request)
{
if ((request.Text ?? "").Length == 0)
{
@@ -37,14 +36,14 @@ public abstract class AbstractDialogChatBot(
&& commando.GetCommands().Contains(splitCommand[1])
)
{
return commando.Handle(splitCommand[1], GetChatBot(), sessionId, request);
return await commando.Handle(splitCommand[1], GetChatBot(), sessionId, request);
}
if (
string.Equals(splitCommand.FirstOrDefault(), "help", StringComparison.OrdinalIgnoreCase)
)
{
return SendPlayerHelpMessage(sessionId, request);
return await SendPlayerHelpMessage(sessionId, request);
}
_mailSendService.SendUserMessageToPlayer(
@@ -55,10 +54,13 @@ public abstract class AbstractDialogChatBot(
null
);
return null;
return string.Empty;
}
protected string? SendPlayerHelpMessage(string sessionId, SendMessageRequest request)
protected async ValueTask<string> SendPlayerHelpMessage(
string sessionId,
SendMessageRequest request
)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
@@ -67,40 +69,34 @@ public abstract class AbstractDialogChatBot(
[],
null
);
// due to BSG being dumb with messages we need a mandatory timeout between messages so they get out on the right order
TimeoutCallback.RunInTimespan(
() =>
{
foreach (var chatCommand in _chatCommands.Values)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
$"Commands available for \"{chatCommand.GetCommandPrefix()}\" prefix:",
[],
null
);
foreach (var chatCommand in _chatCommands.Values)
{
// due to BSG being dumb with messages we need a mandatory timeout between messages so they get out on the right order
await Task.Delay(TimeSpan.FromSeconds(1));
TimeoutCallback.RunInTimespan(
() =>
{
foreach (var subCommand in chatCommand.GetCommands())
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
$"Subcommand {subCommand}:\n{chatCommand.GetCommandHelp(subCommand)}",
[],
null
);
}
},
TimeSpan.FromSeconds(1)
);
}
},
TimeSpan.FromSeconds(1)
);
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
$"Commands available for \"{chatCommand.GetCommandPrefix()}\" prefix:",
[],
null
);
await Task.Delay(TimeSpan.FromSeconds(1));
foreach (var subCommand in chatCommand.GetCommands())
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
$"Subcommand {subCommand}:\n{chatCommand.GetCommandHelp(subCommand)}",
[],
null
);
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
return request.DialogId;
}
@@ -8,7 +8,7 @@ public interface IChatCommand
public string GetCommandPrefix();
public string GetCommandHelp(string command);
public List<string> GetCommands();
public string Handle(
public ValueTask<string> Handle(
string command,
UserDialogInfo commandHandler,
string sessionId,
@@ -50,14 +50,14 @@ public class SptCommandoCommands : IChatCommand
return _sptCommands.Keys.ToList();
}
public string Handle(
public async ValueTask<string> Handle(
string command,
UserDialogInfo commandHandler,
string sessionId,
SendMessageRequest request
)
{
return _sptCommands[command].PerformAction(commandHandler, sessionId, request);
return await _sptCommands[command].PerformAction(commandHandler, sessionId, request);
}
public void RegisterSptCommandoCommand(ISptCommand command)
@@ -52,7 +52,7 @@ public class GiveSptCommand(
+ "give [locale] [\"item name\"] [quantity]\n\t\tEx: spt give fr \"figurine de chat\" 3";
}
public string PerformAction(
public ValueTask<string> PerformAction(
UserDialogInfo commandHandler,
string sessionId,
SendMessageRequest request
@@ -65,7 +65,7 @@ public class GiveSptCommand(
commandHandler,
"Invalid use of give command. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
var result = _commandRegex.Match(request.Text);
@@ -86,7 +86,7 @@ public class GiveSptCommand(
commandHandler,
"Invalid use of give command. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
_savedCommand.TryGetValue(sessionId, out var savedCommand);
@@ -98,7 +98,7 @@ public class GiveSptCommand(
commandHandler,
"Invalid selection. Outside of bounds! Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
item = savedCommand.PotentialItemNames[locationSixValue - 1];
@@ -128,7 +128,7 @@ public class GiveSptCommand(
commandHandler,
"Invalid quantity! Must be 1 or higher. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
if (isItemName)
@@ -157,7 +157,7 @@ public class GiveSptCommand(
.Select(i =>
localizedGlobal
.GetValueOrDefault($"{i.Id} Name", i.Properties.Name)
?.ToLower()
?.ToLowerInvariant()
)
.Where(i => !string.IsNullOrEmpty(i));
@@ -197,7 +197,7 @@ public class GiveSptCommand(
$"Could not find exact match. Closest are:\n{string.Join("\n", itemList)}\n\nUse 'spt give [above number]' to select one."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
}
}
@@ -210,7 +210,8 @@ public class GiveSptCommand(
.GetItems()
.Where(IsItemAllowed)
.FirstOrDefault(i =>
(localizedGlobal[$"{i?.Id} Name"]?.ToLower() ?? i.Properties.Name) == item
(localizedGlobal[$"{i?.Id} Name"]?.ToLowerInvariant() ?? i.Properties.Name)
== item
)
.Id
: item;
@@ -223,7 +224,7 @@ public class GiveSptCommand(
commandHandler,
"That item could not be found. Please refine your request and try again."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
List<Item> itemsToSend = [];
@@ -285,7 +286,7 @@ public class GiveSptCommand(
"Too many items requested. Please lower the amount and try again."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
}
}
@@ -299,7 +300,7 @@ public class GiveSptCommand(
itemsToSend
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
/// <summary>
@@ -14,8 +14,8 @@ public static class StringSimilarity
{
if (!caseSensitive)
{
str1 = str1.ToLower();
str2 = str2.ToLower();
str1 = str1.ToLowerInvariant();
str2 = str2.ToLowerInvariant();
}
if (str1.Length < substringLength || str2.Length < substringLength)
@@ -7,7 +7,7 @@ public interface ISptCommand
{
public string GetCommand();
public string GetCommandHelp();
public string PerformAction(
public ValueTask<string> PerformAction(
UserDialogInfo commandHandler,
string sessionId,
SendMessageRequest request
@@ -9,14 +9,12 @@ using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Dialog;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Helpers.Dialogue.Commando.SptCommands.ProfileCommand;
[Injectable]
public class ProfileSptCommand(
ISptLogger<ProfileSptCommand> _logger,
HashUtil _hashUtil,
MailSendService _mailSendService,
ProfileHelper _profileHelper
) : ISptCommand
@@ -44,7 +42,7 @@ public class ProfileSptCommand(
+ "spt profile skill metabolism 51";
}
public string PerformAction(
public ValueTask<string> PerformAction(
UserDialogInfo commandHandler,
string sessionId,
SendMessageRequest request
@@ -60,7 +58,7 @@ public class ProfileSptCommand(
commandHandler,
"Invalid use of trader command. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
var result = _commandRegex.Match(request.Text);
@@ -84,7 +82,7 @@ public class ProfileSptCommand(
commandHandler,
"Invalid use of profile command, the level was outside bounds: 1 to 70. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
profileChangeEvent = HandleLevelCommand(quantity);
@@ -104,7 +102,7 @@ public class ProfileSptCommand(
commandHandler,
"Invalid use of profile command, the skill was not found. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
if (quantity is < 0 or > 51)
@@ -114,7 +112,7 @@ public class ProfileSptCommand(
commandHandler,
"Invalid use of profile command, the skill level was outside bounds: 1 to 51. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
profileChangeEvent = HandleSkillCommand(enumSkill, quantity);
@@ -131,7 +129,7 @@ public class ProfileSptCommand(
commandHandler,
$"If you are reading this, this is bad. Please report this to SPT staff with a screenshot. Command: {command}."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
_mailSendService.SendSystemMessageToPlayer(
@@ -143,7 +141,7 @@ public class ProfileSptCommand(
Id = new MongoId(),
Template = Money.ROUBLES,
Upd = new Upd { StackObjectsCount = 1 },
ParentId = _hashUtil.Generate(),
ParentId = new MongoId(),
SlotId = "main",
},
],
@@ -151,7 +149,7 @@ public class ProfileSptCommand(
[profileChangeEvent]
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
protected ProfileChangeEvent HandleSkillCommand(SkillTypes? skill, int level)
@@ -10,14 +10,12 @@ using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Dialog;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Helpers.Dialogue.Commando.SptCommands.TraderCommand;
[Injectable]
public class TraderSptCommand(
ISptLogger<TraderSptCommand> _logger,
HashUtil _hashUtil,
TraderHelper _traderHelper,
MailSendService _mailSendService
) : ISptCommand
@@ -36,7 +34,7 @@ public class TraderSptCommand(
return "spt trader \n ======== \n Sets the reputation or money spent to the input quantity through the message system.\n\n\tspt trader [trader] rep [quantity]\n\t\tEx: spt trader prapor rep 2\n\n\tspt trader [trader] spend [quantity]\n\t\tEx: spt trader therapist spend 1000000";
}
public string PerformAction(
public ValueTask<string> PerformAction(
UserDialogInfo commandHandler,
string sessionId,
SendMessageRequest request
@@ -49,7 +47,7 @@ public class TraderSptCommand(
commandHandler,
"Invalid use of trader command. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
var result = _commandRegex.Match(request.Text);
@@ -77,7 +75,7 @@ public class TraderSptCommand(
"Invalid use of trader command, the trader was not found. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
NotificationEventType profileChangeEventType;
@@ -98,7 +96,7 @@ public class TraderSptCommand(
"Invalid use of trader command, ProfileChangeEventType was not found. Use 'help' for more information."
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
}
@@ -111,7 +109,7 @@ public class TraderSptCommand(
Id = new MongoId(),
Template = Money.ROUBLES,
Upd = new Upd { StackObjectsCount = 1 },
ParentId = _hashUtil.Generate(),
ParentId = new MongoId(),
SlotId = "main",
},
],
@@ -119,7 +117,7 @@ public class TraderSptCommand(
[CreateProfileChangeEvent(profileChangeEventType, quantity, dbTrader.Id)]
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
protected ProfileChangeEvent CreateProfileChangeEvent(
@@ -6,5 +6,10 @@ namespace SPTarkov.Server.Core.Helpers.Dialogue;
public interface IDialogueChatBot
{
public UserDialogInfo GetChatBot();
public string? HandleMessage(string sessionId, SendMessageRequest request);
/// <summary>
/// Handles messages for the chatbot. If a message can't be handled, <see cref="string.Empty"/> should be used.
/// </summary>
/// <returns>The response of the bot, or <see cref="string.Empty"/> if the request could not be handled.</returns>
public ValueTask<string> HandleMessage(string sessionId, SendMessageRequest request);
}
@@ -13,9 +13,7 @@ namespace SPTarkov.Server.Core.Helpers.Dialogue;
[Injectable]
public class SptDialogueChatBot(
ISptLogger<AbstractDialogChatBot> _logger,
MailSendService _mailSendService,
IEnumerable<IChatCommand> _chatCommands,
ConfigServer _configServer,
ProfileHelper _profileHelper,
IEnumerable<IChatMessageHandler> chatMessageHandlers
@@ -42,7 +40,7 @@ public class SptDialogueChatBot(
};
}
public string? HandleMessage(string sessionId, SendMessageRequest request)
public ValueTask<string> HandleMessage(string sessionId, SendMessageRequest request)
{
var sender = _profileHelper.GetPmcProfile(sessionId);
var sptFriendUser = GetChatBot();
@@ -57,7 +55,7 @@ public class SptDialogueChatBot(
{
handler.Process(sessionId, sptFriendUser, sender, request);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
_mailSendService.SendUserMessageToPlayer(
@@ -68,7 +66,7 @@ public class SptDialogueChatBot(
null
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
protected static List<IChatMessageHandler> ChatMessageHandlerSetup(
@@ -86,7 +84,7 @@ public class SptDialogueChatBot(
return "Unknown command.";
}
protected string? SendPlayerHelpMessage(string sessionId, SendMessageRequest request)
protected ValueTask<string> SendPlayerHelpMessage(string sessionId, SendMessageRequest request)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
@@ -96,6 +94,6 @@ public class SptDialogueChatBot(
null
);
return request.DialogId;
return new ValueTask<string>(request.DialogId);
}
}
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Utils;
@@ -46,7 +47,7 @@ public class DialogueHelper(ISptLogger<DialogueHelper> logger, ProfileHelper pro
/// <param name="sessionID">Session/player id</param>
/// <param name="itemId">Item being moved to inventory</param>
/// <returns>Collection of items from message</returns>
public List<Item> GetMessageItemContents(string messageID, string sessionID, string itemId)
public List<Item> GetMessageItemContents(string messageID, string sessionID, MongoId itemId)
{
var fullProfile = profileHelper.GetFullProfile(sessionID);
var dialogueData = fullProfile.DialogueRecords;
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
@@ -88,7 +89,7 @@ public class HandbookHelper(
/// </summary>
/// <param name="tpl">Item tpl to look up price for</param>
/// <returns>price in roubles</returns>
public double GetTemplatePrice(string tpl)
public double GetTemplatePrice(MongoId tpl)
{
if (HandbookPriceCache.Items.ById.TryGetValue(tpl, out var itemPrice))
{
@@ -211,11 +212,11 @@ public class HandbookHelper(
{
public LookupItem()
{
ById = new Dictionary<string, T>();
ById = new Dictionary<MongoId, T>();
ByParent = new Dictionary<string, List<I>>();
}
public Dictionary<string, T> ById { get; set; }
public Dictionary<MongoId, T> ById { get; set; }
public Dictionary<string, List<I>> ByParent { get; set; }
}
@@ -21,7 +21,6 @@ public class HideoutHelper(
ISptLogger<HideoutHelper> _logger,
TimeUtil _timeUtil,
ServerLocalisationService _serverLocalisationService,
HashUtil _hashUtil,
DatabaseService _databaseService,
EventOutputHolder _eventOutputHolder,
HttpResponseUtil _httpResponseUtil,
@@ -31,10 +30,8 @@ public class HideoutHelper(
ICloner _cloner
)
{
public const string BitcoinFarm = "5d5c205bd582a50d042a3c0e";
public const string CultistCircleCraftId = "66827062405f392b203a44cf";
public const string BitcoinProductionId = "5d5c205bd582a50d042a3c0e";
public const string WaterCollector = "5d5589c1f934db045e6c5492";
public static readonly MongoId BitcoinProductionId = new("5d5c205bd582a50d042a3c0e");
public static readonly MongoId WaterCollectorId = new("5d5589c1f934db045e6c5492");
public const int MaxSkillPoint = 5000;
/// <summary>
@@ -599,8 +596,7 @@ public class HideoutHelper(
* GetTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn);
// Get all fuel consumption bonuses, returns an empty array if none found
var profileFuelConsomptionBonusSum = _profileHelper.GetBonusValueFromProfile(
pmcData,
var profileFuelConsomptionBonusSum = pmcData.GetBonusValueFromProfile(
BonusType.FuelConsumption
);
@@ -733,7 +729,7 @@ public class HideoutHelper(
// Canister with purified water craft exists
if (
pmcData.Hideout.Production.TryGetValue(WaterCollector, out var purifiedWaterCraft)
pmcData.Hideout.Production.TryGetValue(WaterCollectorId, out var purifiedWaterCraft)
&& purifiedWaterCraft.GetType() == typeof(Production)
)
{
@@ -752,7 +748,7 @@ public class HideoutHelper(
// seem to not trigger consistently
var recipe = new HideoutSingleProductionStartRequestData
{
RecipeId = WaterCollector,
RecipeId = WaterCollectorId,
Action = "HideoutSingleProductionStart",
Items = [],
Tools = [],
@@ -793,7 +789,7 @@ public class HideoutHelper(
var timeReductionSeconds = 0D;
// Bitcoin farm is excluded from crafting skill cooldown reduction
if (recipeId != BitcoinFarm)
if (recipeId != BitcoinProductionId)
// Seconds to deduct from crafts total time
{
timeReductionSeconds += GetSkillProductionTimeReduction(
@@ -845,7 +841,7 @@ public class HideoutHelper(
)
{
var filterDrainRate = GetWaterFilterDrainRate(pmcData);
var craftProductionTime = GetTotalProductionTimeSeconds(WaterCollector);
var craftProductionTime = GetTotalProductionTimeSeconds(WaterCollectorId);
var secondsSinceServerTick = GetTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn);
filterDrainRate = GetTimeAdjustedWaterFilterDrainRate(
@@ -1286,7 +1282,7 @@ public class HideoutHelper(
{
var bitcoinProductions = _databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault(production => production.Id == BitcoinFarm);
.Production.Recipes.FirstOrDefault(production => production.Id == BitcoinProductionId);
var productionSlots = bitcoinProductions?.ProductionLimitCount ?? 3; // Default to 3 if none found
var hasManagementSkillSlots = _profileHelper.HasEliteSkillLevel(
SkillTypes.HideoutManagement,
@@ -1401,7 +1397,7 @@ public class HideoutHelper(
)
{
// Get how many coins were crafted and ready to pick up
pmcData.Hideout.Production.TryGetValue(BitcoinFarm, out var bitcoinCraft);
pmcData.Hideout.Production.TryGetValue(BitcoinProductionId, out var bitcoinCraft);
var craftedCoinCount = bitcoinCraft?.Products?.Count;
if (bitcoinCraft is null || craftedCoinCount is null)
{
@@ -1446,15 +1442,16 @@ public class HideoutHelper(
// Is at max capacity + we collected all coins - reset production start time
var coinSlotCount = GetBTCSlots(pmcData);
if (pmcData.Hideout.Production[BitcoinFarm].Products.Count >= coinSlotCount)
if (pmcData.Hideout.Production[BitcoinProductionId].Products.Count >= coinSlotCount)
// Set start to now
{
pmcData.Hideout.Production[BitcoinFarm].StartTimestamp = _timeUtil.GetTimeStamp();
pmcData.Hideout.Production[BitcoinProductionId].StartTimestamp =
_timeUtil.GetTimeStamp();
}
// Remove crafted coins from production in profile now they've been collected
// Can only collect all coins, not individually
pmcData.Hideout.Production[BitcoinFarm].Products = [];
pmcData.Hideout.Production[BitcoinProductionId].Products = [];
}
/// <summary>
@@ -14,7 +14,6 @@ namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class InRaidHelper(
InventoryHelper _inventoryHelper,
ItemHelper _itemHelper,
ConfigServer _configServer,
ICloner _cloner,
DatabaseService _databaseService
@@ -66,23 +65,27 @@ public class InRaidHelper(
var insured = _cloner.Clone(serverProfile.InsuredItems);
// Remove equipment and loot items stored on player from server profile in preparation for data from client being added
_inventoryHelper.RemoveItem(serverProfile, serverProfile.Inventory.Equipment, sessionID);
_inventoryHelper.RemoveItem(
serverProfile,
serverProfile.Inventory.Equipment.Value,
sessionID
);
// Remove quest items stored on player from server profile in preparation for data from client being added
_inventoryHelper.RemoveItem(
serverProfile,
serverProfile.Inventory.QuestRaidItems,
serverProfile.Inventory.QuestRaidItems.Value,
sessionID
);
// Get all items that have a parent of `serverProfile.Inventory.equipment` (All items player had on them at end of raid)
var postRaidInventoryItems = postRaidProfile.Inventory.Items.FindAndReturnChildrenAsItems(
postRaidProfile.Inventory.Equipment
postRaidProfile.Inventory.Equipment.Value
);
// Get all items that have a parent of `serverProfile.Inventory.questRaidItems` (Quest items player had on them at end of raid)
var postRaidQuestItems = postRaidProfile.Inventory.Items.FindAndReturnChildrenAsItems(
postRaidProfile.Inventory.QuestRaidItems
postRaidProfile.Inventory.QuestRaidItems.Value
);
// Handle Removing of FIR status if player did not survive + not transferring
@@ -243,14 +246,10 @@ public class InRaidHelper(
}
// Pocket items are lost on death
// Ensure we dont pick up pocket items from manniquins
// Ensure we don't pick up pocket items from mannequins
if (
item.SlotId.StartsWith("pocket")
&& _inventoryHelper.DoesItemHaveRootId(
pmcProfile,
item,
pmcProfile.Inventory.Equipment
)
&& pmcProfile.DoesItemHaveRootId(item, pmcProfile.Inventory.Equipment)
)
{
return true;
@@ -1,9 +1,8 @@
using System.Collections.Frozen;
using System.Text.Json;
using System.Text.Json.Serialization;
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Inventory;
@@ -24,10 +23,8 @@ namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class InventoryHelper(
ISptLogger<InventoryHelper> _logger,
HashUtil _hashUtil,
HttpResponseUtil _httpResponseUtil,
DialogueHelper _dialogueHelper,
ContainerHelper _containerHelper,
EventOutputHolder _eventOutputHolder,
ProfileHelper _profileHelper,
ItemHelper _itemHelper,
@@ -36,7 +33,7 @@ public class InventoryHelper(
ICloner _cloner
)
{
private static readonly FrozenSet<string> _variableSizeItemTypes =
private static readonly FrozenSet<MongoId> _variableSizeItemTypes =
[
BaseClasses.WEAPON,
BaseClasses.FUNCTIONAL_MOD,
@@ -108,7 +105,7 @@ public class InventoryHelper(
var itemWithModsToAddClone = _cloner.Clone(request.ItemWithModsToAdd);
// Get stash layouts ready for use
var stashFS2D = GetStashSlotMap(pmcData, sessionId);
var stashFS2D = GetStashSlotMap(pmcData);
if (stashFS2D is null)
{
_logger.Error($"Unable to get stash map for players: {sessionId} stash");
@@ -145,10 +142,7 @@ public class InventoryHelper(
// Run callback
try
{
if (request.Callback is not null)
{
request.Callback((int)(itemWithModsToAddClone[0].Upd.StackObjectsCount ?? 0));
}
request.Callback?.Invoke((int)(itemWithModsToAddClone[0].Upd.StackObjectsCount ?? 0));
}
catch (Exception ex)
{
@@ -168,7 +162,7 @@ public class InventoryHelper(
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Added {itemWithModsToAddClone[0].Upd?.StackObjectsCount ?? 1} item: {itemWithModsToAddClone[0].Template} with: {itemWithModsToAddClone.Count - 1} mods to inventory"
$"Added: {itemWithModsToAddClone[0].Upd?.StackObjectsCount ?? 1} item: {itemWithModsToAddClone[0].Template} with: {itemWithModsToAddClone.Count - 1} mods to inventory"
);
}
}
@@ -224,7 +218,7 @@ public class InventoryHelper(
{
var pmcData = _profileHelper.GetPmcProfile(sessionId);
var stashFS2D = _cloner.Clone(GetStashSlotMap(pmcData, sessionId));
var stashFS2D = _cloner.Clone(GetStashSlotMap(pmcData));
if (stashFS2D is null)
{
_logger.Error($"Unable to get stash map for players: {sessionId} stash");
@@ -244,7 +238,7 @@ public class InventoryHelper(
/// <param name="containerFS2D">Container grid to fit items into</param>
/// <param name="itemsWithChildren">Items to try and fit into grid</param>
/// <returns>True all fit</returns>
public bool CanPlaceItemsInContainer(int[][] containerFS2D, List<List<Item>> itemsWithChildren)
public bool CanPlaceItemsInContainer(int[,] containerFS2D, List<List<Item>> itemsWithChildren)
{
return itemsWithChildren.All(itemWithChildren =>
CanPlaceItemInContainer(containerFS2D, itemWithChildren)
@@ -257,28 +251,23 @@ public class InventoryHelper(
/// <param name="containerFS2D">Container grid</param>
/// <param name="itemWithChildren">Item to check fits</param>
/// <returns>True it fits</returns>
public bool CanPlaceItemInContainer(int[][] containerFS2D, List<Item> itemWithChildren)
public bool CanPlaceItemInContainer(int[,] containerFS2D, List<Item> itemWithChildren)
{
// Get x/y size of item
var rootItem = itemWithChildren[0];
var itemSize = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren);
var (sizeX, sizeY) = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren);
// Look for a place to slot item into
var findSlotResult = _containerHelper.FindSlotForItem(
containerFS2D,
itemSize[0],
itemSize[1]
);
var findSlotResult = containerFS2D.FindSlotForItem(sizeX, sizeY);
if (findSlotResult.Success.GetValueOrDefault(false))
{
try
{
_containerHelper.FillContainerMapWithItem(
containerFS2D,
containerFS2D.FillContainerMapWithItem(
findSlotResult.X.Value,
findSlotResult.Y.Value,
itemSize[0],
itemSize[1],
sizeX,
sizeY,
findSlotResult.Rotation.Value
);
}
@@ -309,7 +298,7 @@ public class InventoryHelper(
/// <param name="containerId">Id of the container we're fitting item into</param>
/// <param name="desiredSlotId">Slot id value to use, default is "hideout"</param>
public void PlaceItemInContainer(
int[][] containerFS2D,
int[,] containerFS2D,
List<Item> itemWithChildren,
string containerId,
string desiredSlotId = "hideout"
@@ -317,24 +306,23 @@ public class InventoryHelper(
{
// Get x/y size of item
var rootItemAdded = itemWithChildren[0];
var itemSize = GetItemSize(rootItemAdded.Template, rootItemAdded.Id, itemWithChildren);
var (sizeX, sizeY) = GetItemSize(
rootItemAdded.Template,
rootItemAdded.Id,
itemWithChildren
);
// Look for a place to slot item into
var findSlotResult = _containerHelper.FindSlotForItem(
containerFS2D,
itemSize[0],
itemSize[1]
);
var findSlotResult = containerFS2D.FindSlotForItem(sizeX, sizeY);
if (findSlotResult.Success.GetValueOrDefault(false))
{
try
{
_containerHelper.FillContainerMapWithItem(
containerFS2D,
containerFS2D.FillContainerMapWithItem(
findSlotResult.X.Value,
findSlotResult.Y.Value,
itemSize[0],
itemSize[1],
sizeX,
sizeY,
findSlotResult.Rotation.Value
);
}
@@ -370,15 +358,15 @@ public class InventoryHelper(
/// <summary>
/// Find a location to place an item into inventory and place it
/// </summary>
/// <param name="stashFS2D">2-dimensional representation of the container slots</param>
/// <param name="stashFS2D">2-dimensional representation of the container</param>
/// <param name="sortingTableFS2D">2-dimensional representation of the sorting table slots</param>
/// <param name="itemWithChildren">Item to place with children</param>
/// <param name="playerInventory">Players inventory</param>
/// <param name="useSortingTable">Should sorting table to be used if main stash has no space</param>
/// <param name="output">Output to send back to client</param>
protected void PlaceItemInInventory(
int[][] stashFS2D,
int[][] sortingTableFS2D,
int[,] stashFS2D,
int[,] sortingTableFS2D,
List<Item> itemWithChildren,
BotBaseInventory playerInventory,
bool useSortingTable,
@@ -387,20 +375,19 @@ public class InventoryHelper(
{
// Get x/y size of item
var rootItem = itemWithChildren[0];
var itemSize = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren);
var (sizeX, sizeY) = GetItemSize(rootItem.Template, rootItem.Id, itemWithChildren);
// Look for a place to slot item into
var findSlotResult = _containerHelper.FindSlotForItem(stashFS2D, itemSize[0], itemSize[1]);
var findSlotResult = stashFS2D.FindSlotForItem(sizeX, sizeY);
if (findSlotResult.Success.Value)
{
try
{
_containerHelper.FillContainerMapWithItem(
stashFS2D,
stashFS2D.FillContainerMapWithItem(
findSlotResult.X.Value,
findSlotResult.Y.Value,
itemSize[0],
itemSize[1],
sizeX,
sizeY,
findSlotResult.Rotation.Value
);
}
@@ -411,7 +398,7 @@ public class InventoryHelper(
return;
}
// Store details for object, incuding container item will be placed in
// Store details for object, including container item will be placed in
rootItem.ParentId = playerInventory.Stash;
rootItem.SlotId = "hideout";
rootItem.Location = new ItemLocation
@@ -429,20 +416,15 @@ public class InventoryHelper(
// Space not found in main stash, use sorting table
if (useSortingTable)
{
var findSortingSlotResult = _containerHelper.FindSlotForItem(
sortingTableFS2D,
itemSize[0],
itemSize[1]
);
var findSortingSlotResult = sortingTableFS2D.FindSlotForItem(sizeX, sizeY);
try
{
_containerHelper.FillContainerMapWithItem(
sortingTableFS2D,
sortingTableFS2D.FillContainerMapWithItem(
findSortingSlotResult.X.Value,
findSortingSlotResult.Y.Value,
itemSize[0],
itemSize[1],
sizeX,
sizeY,
findSortingSlotResult.Rotation.Value
);
}
@@ -498,12 +480,12 @@ public class InventoryHelper(
/// <param name="output">OPTIONAL - ItemEventRouterResponse</param>
public void RemoveItem(
PmcData profile,
string? itemId,
MongoId itemId,
string sessionId,
ItemEventRouterResponse? output = null
)
{
if (itemId is null)
if (itemId.IsEmpty())
{
_logger.Warning(
_serverLocalisationService.GetText("inventory-unable_to_remove_item_no_id_given")
@@ -533,10 +515,7 @@ public class InventoryHelper(
var insuredItems = profile.InsuredItems;
// We have output object, inform client of root item deletion, not children
if (output is not null)
{
output.ProfileChanges[sessionId].Items.DeletedItems.Add(new Item { Id = itemId });
}
output?.ProfileChanges[sessionId].Items.DeletedItems.Add(new DeletedItem { Id = itemId });
foreach (var item in itemAndChildrenToRemove)
{
@@ -638,13 +617,13 @@ public class InventoryHelper(
/// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse RemoveItemByCount(
PmcData pmcData,
string? itemId,
MongoId itemId,
int countToRemove,
string sessionId,
ItemEventRouterResponse? output
)
{
if (itemId is null)
if (itemId.IsEmpty())
{
return output;
}
@@ -689,7 +668,7 @@ public class InventoryHelper(
/// <param name="itemId">Items id to get size of</param>
/// <param name="inventoryItems"></param>
/// <returns>[width, height]</returns>
public List<int> GetItemSize(string? itemTpl, string itemId, List<Item> inventoryItems)
public (int, int) GetItemSize(MongoId itemTpl, MongoId itemId, List<Item> inventoryItems)
{
// -> Prepares item Width and height returns [sizeX, sizeY]
return GetSizeByInventoryItemHash(itemTpl, itemId, GetInventoryItemHash(inventoryItems));
@@ -703,9 +682,9 @@ public class InventoryHelper(
/// <param name="itemId">Items id</param>
/// <param name="inventoryItemHash">Hashmap of inventory items</param>
/// <returns>An array representing the [width, height] of the item</returns>
protected List<int> GetSizeByInventoryItemHash(
string itemTpl,
string itemId,
protected (int, int) GetSizeByInventoryItemHash(
MongoId itemTpl,
MongoId itemId,
InventoryItemHash inventoryItemHash
)
{
@@ -738,7 +717,7 @@ public class InventoryHelper(
_serverLocalisationService.GetText("inventory-return_default_size", itemTpl)
);
return [1, 1]; // Invalid input data, return defaults
return (1, 1); // Invalid input data, return defaults
}
if (!inventoryItemHash.ByItemId.TryGetValue(itemId, out var rootItem))
@@ -747,7 +726,7 @@ public class InventoryHelper(
$"Unable to get root item with Id: {itemId} from player inventory. Defaulting to 1x1"
);
return [1, 1]; // Invalid input data, return defaults
return (1, 1); // Invalid input data, return defaults
}
// Does root item support being folded
@@ -869,104 +848,118 @@ public class InventoryHelper(
}
}
return
[
return (
outX.Value + sizeLeft + sizeRight + forcedLeft + forcedRight,
outY.Value + sizeUp + sizeDown + forcedUp + forcedDown,
];
outY.Value + sizeUp + sizeDown + forcedUp + forcedDown
);
}
/// <summary>
/// Get a 2d mapping of a container with what grid slots are filled
/// </summary>
/// <param name="sizeX">Horizontal size of container</param>
/// <param name="sizeY">Vertical size of container</param>
/// <param name="containerSizeHorizontalX">Horizontal (Column) size of container</param>
/// <param name="containerSizeVerticalY">Vertical (Row) size of container</param>
/// <param name="itemList">Players inventory items</param>
/// <param name="containerId">Id of the container</param>
/// <returns>Two-dimensional representation of container</returns>
public int[][] GetContainerMap(int sizeX, int sizeY, List<Item> itemList, string containerId)
public int[,] GetContainerMap(
int containerSizeHorizontalX,
int containerSizeVerticalY,
List<Item> itemList,
string containerId
)
{
// Create blank 2d map of container
var containerYX = _itemHelper.GetBlankContainerMap(sizeY, sizeX);
var container = _itemHelper.GetBlankContainerMap(
containerSizeHorizontalX, // Column count
containerSizeVerticalY // Row count
);
// Get all items in players inventory keyed by their parentId and by ItemId
var inventoryItemHash = GetInventoryItemHash(itemList);
// Get subset of items that belong to the desired container
if (!inventoryItemHash.ByParentId.TryGetValue(containerId, out var rootItemsInContainer))
// No items in container, exit early
{
return containerYX;
// No items in container, exit early and return the blank container map
return container;
}
// Check each item in container
foreach (var item in rootItemsInContainer)
// Add every root items size (with mods attached) found in container
foreach (var rootItem in rootItemsInContainer)
{
ItemLocation? itemLocation;
if (item.Location is JsonElement element)
{
// TODO: is this ever true?
itemLocation = element.ToObject<ItemLocation>();
}
else
{
itemLocation = (ItemLocation?)item.Location;
}
var itemLocation = rootItem.GetParsedLocation();
if (itemLocation is null)
{
// Item has no location property
_logger.Error(
$"Unable to find 'location' property on item with id: {item.Id}, skipping"
$"Unable to find 'location' property on item with id: {rootItem.Id}, skipping"
);
continue;
}
// Get x/y size of item
var tmpSize = GetSizeByInventoryItemHash(item.Template, item.Id, inventoryItemHash);
var iW = tmpSize[0]; // x
var iH = tmpSize[1]; // y
var fH = itemLocation.IsVertical() ? iW : iH;
var fW = itemLocation.IsVertical() ? iH : iW;
// Get x/y size of item (without rotation)
var (rawItemXWidth, rawItemYHeight) = GetSizeByInventoryItemHash(
rootItem.Template,
rootItem.Id,
inventoryItemHash
);
// Items horizontal size
var itemHeight = itemLocation.IsVertical() ? rawItemXWidth : rawItemYHeight;
for (var y = 0; y < fH; y++)
// Items vertical size
var itemWidth = itemLocation.IsVertical() ? rawItemYHeight : rawItemXWidth;
// vertical (row)
for (var yOffset = 0; yOffset < itemHeight; yOffset++)
{
try
// horizontal (column)
for (var xOffset = 0; xOffset < itemWidth; xOffset++)
{
var rowIndex = itemLocation.Y + y;
var containerX = containerYX.ElementAtOrDefault(rowIndex.Value);
if (containerX is null)
{
_logger.Error(
$"Unable to find container: {containerId} row line: {itemLocation.Y + y}"
);
}
var currentY = itemLocation.Y.Value + yOffset;
var currentX = itemLocation.X.Value + xOffset;
// Fill the corresponding cells in the container map to show the slot is taken
Array.Fill(containerX, 1, itemLocation.X.Value, fW);
}
catch (Exception ex)
{
_logger.Error(
_serverLocalisationService.GetText(
"inventory-unable_to_fill_container",
new { id = item.Id, error = $"{ex.Message} {ex.StackTrace}" }
)
);
// Check still in containers bounds
if (
currentY >= 0
&& currentY < containerSizeVerticalY
&& currentX >= 0
&& currentX < containerSizeHorizontalX
)
{
// mark slot used
container[currentY, currentX] = 1;
}
else
{
// Out of bounds
var message =
$"Item: {rootItem.Id} at: {itemLocation.X}, {itemLocation.Y} size: {itemHeight}x{itemWidth} extends outside the containers bounds";
_logger.Error(
_serverLocalisationService.GetText(
"inventory-unable_to_fill_container",
new { id = rootItem.Id, error = $"{message}" }
)
);
// Stop and try next row
break;
}
}
}
}
return containerYX;
return container;
}
protected InventoryItemHash GetInventoryItemHash(List<Item> inventoryItems)
{
var inventoryItemHash = new InventoryItemHash
{
ByItemId = new Dictionary<string, Item>(),
ByParentId = new Dictionary<string, HashSet<Item>>(),
ByItemId = new Dictionary<MongoId, Item>(),
ByParentId = new Dictionary<MongoId, HashSet<Item>>(),
};
foreach (var item in inventoryItems)
{
@@ -977,6 +970,11 @@ public class InventoryHelper(
continue;
}
if (item.ParentId == "hideout")
{
continue;
}
if (!inventoryItemHash.ByParentId.ContainsKey(item.ParentId))
{
inventoryItemHash.ByParentId[item.ParentId] = [];
@@ -994,12 +992,12 @@ public class InventoryHelper(
/// Based on the item action, determine whose inventories we should be looking at for from and to.
/// </summary>
/// <param name="request">Item interaction request</param>
/// <param name="item">Item being moved/split/etc to inventory</param>
/// <param name="itemId">Item being moved/split/etc to inventory</param>
/// <param name="sessionId">Session id / players Id</param>
/// <returns>OwnerInventoryItems with inventory of player/scav to adjust</returns>
public OwnerInventoryItems GetOwnerInventoryItems(
InventoryBaseActionRequestData request,
string? item,
MongoId itemId,
string sessionId
)
{
@@ -1023,7 +1021,7 @@ public class InventoryHelper(
fromInventoryItems = _dialogueHelper.GetMessageItemContents(
request.FromOwner.Id,
sessionId,
item
itemId
);
fromType = "mail";
}
@@ -1058,14 +1056,13 @@ public class InventoryHelper(
/// 0 value = free, 1 = taken
/// </summary>
/// <param name="pmcData">Player profile</param>
/// <param name="sessionID">session id</param>
/// <returns>2-dimensional array</returns>
protected int[][]? GetStashSlotMap(PmcData pmcData, string sessionID)
protected int[,] GetStashSlotMap(PmcData pmcData)
{
var playerStashSize = GetPlayerStashSize(sessionID);
var (horizontal, vertical) = GetPlayerStashSize(pmcData);
return GetContainerMap(
playerStashSize[0],
playerStashSize[1],
horizontal,
vertical,
pmcData.Inventory.Items,
pmcData.Inventory.Stash
);
@@ -1076,15 +1073,18 @@ public class InventoryHelper(
/// </summary>
/// <param name="containerTpl">Container to get data for</param>
/// <returns>blank two-dimensional array</returns>
public int[][] GetContainerSlotMap(string containerTpl)
public int[,] GetContainerSlotMap(string containerTpl)
{
var containerTemplate = _itemHelper.GetItem(containerTpl).Value;
var firstContainerGrid = containerTemplate.Properties.Grids.FirstOrDefault();
var containerH = firstContainerGrid.Props.CellsH;
var containerV = firstContainerGrid.Props.CellsV;
var containerRowCount = firstContainerGrid.Props.CellsH;
var containerColumnCount = firstContainerGrid.Props.CellsV;
return _itemHelper.GetBlankContainerMap(containerH.Value, containerV.Value);
return _itemHelper.GetBlankContainerMap(
containerColumnCount.Value,
containerRowCount.Value
);
}
/// <summary>
@@ -1092,7 +1092,7 @@ public class InventoryHelper(
/// </summary>
/// <param name="pmcData">Player profile</param>
/// <returns>two-dimensional array</returns>
protected int[][] GetSortingTableSlotMap(PmcData pmcData)
protected int[,] GetSortingTableSlotMap(PmcData pmcData)
{
return GetContainerMap(10, 45, pmcData.Inventory.Items, pmcData.Inventory.SortingTable);
}
@@ -1100,57 +1100,59 @@ public class InventoryHelper(
/// <summary>
/// Get Players Stash Size
/// </summary>
/// <param name="sessionId">Players id</param>
/// <returns>Dictionary of 2 values, horizontal and vertical stash size</returns>
protected List<int> GetPlayerStashSize(string sessionId)
/// <param name="pmcData">Profile to get stash size of</param>
/// <returns>Horizontal and vertical size of stash</returns>
protected (int, int) GetPlayerStashSize(PmcData pmcData)
{
var profile = _profileHelper.GetPmcProfile(sessionId);
var stashRowBonus = profile.Bonuses.FirstOrDefault(bonus =>
bonus.Type == BonusType.StashRows
);
// this sets automatically a stash size from items.json (it's not added anywhere yet because we still use base stash)
var stashTPL = GetStashType(sessionId);
if (stashTPL is null)
// TODO: what??
// This sets automatically a stash size from items.json (it's not added anywhere yet because we still use base stash)
var stashTpl = GetProfileStashTpl(pmcData);
if (stashTpl is null)
{
_logger.Error(_serverLocalisationService.GetText("inventory-missing_stash_size"));
return (0, 0);
}
var stashItemResult = _itemHelper.GetItem(stashTPL);
if (!stashItemResult.Key)
// Look up details of stash in db
var (isValidItem, stashItemDbItem) = _itemHelper.GetItem(stashTpl);
if (!isValidItem)
{
_logger.Error(
_serverLocalisationService.GetText("inventory-stash_not_found", stashTPL)
_serverLocalisationService.GetText("inventory-stash_not_found", stashTpl)
);
return new List<int>();
return (0, 0);
}
var stashItemDetails = stashItemResult.Value;
var firstStashItemGrid = stashItemDetails.Properties.Grids[0];
// Find the main 'grid' of the stash we can use to get size
var firstStashItemGrid = stashItemDbItem?.Properties?.Grids?.FirstOrDefault();
// Get horizontal and vertical size
var stashH = firstStashItemGrid.Props.CellsH != 0 ? firstStashItemGrid.Props.CellsH : 10;
var stashV = firstStashItemGrid.Props.CellsV != 0 ? firstStashItemGrid.Props.CellsV : 66;
// Player has a bonus, apply to vertical size
var stashRowBonus = pmcData.Bonuses.FirstOrDefault(bonus =>
bonus.Type == BonusType.StashRows
);
if (stashRowBonus is not null)
{
stashV += (int)stashRowBonus.Value;
}
return [stashH.Value, stashV.Value];
return (stashH.Value, stashV.Value);
}
/// <summary>
/// Get the players stash items tpl
/// </summary>
/// <param name="sessionId">Player id</param>
/// <param name="profile">Profile to get tpl</param>
/// <returns>Stash tpl</returns>
protected string? GetStashType(string sessionId)
protected string? GetProfileStashTpl(PmcData profile)
{
var pmcData = _profileHelper.GetPmcProfile(sessionId);
var stashObj = pmcData.Inventory.Items.FirstOrDefault(item =>
item.Id == pmcData.Inventory.Stash
var stashObj = profile.Inventory.Items.FirstOrDefault(item =>
item.Id == profile.Inventory.Stash
);
if (stashObj is null)
{
@@ -1172,7 +1174,7 @@ public class InventoryHelper(
InventoryMoveRequestData request
)
{
HandleCartridges(sourceItems, request);
HandleCartridgeMove(sourceItems, request);
// Get all children item has, they need to move with item
var idsToMove = sourceItems.FindAndReturnChildrenByItems(request.Item);
@@ -1229,7 +1231,7 @@ public class InventoryHelper(
)
{
errorMessage = string.Empty;
HandleCartridges(inventoryItems, moveRequest);
HandleCartridgeMove(inventoryItems, moveRequest);
// Find item we want to 'move'
var matchingInventoryItem = inventoryItems.FirstOrDefault(item =>
@@ -1322,7 +1324,7 @@ public class InventoryHelper(
// Reset fast panel value if item was moved to a container other than pocket/rig (cant be used from fastpanel)
HashSet<string> slots = ["pockets", "tacticalvest"];
var wasMovedToFastPanelAccessibleContainer = slots.Contains(
itemParent?.SlotId?.ToLower() ?? ""
itemParent?.SlotId?.ToLowerInvariant() ?? ""
);
if (!wasMovedToFastPanelAccessibleContainer)
{
@@ -1331,9 +1333,11 @@ public class InventoryHelper(
}
/// <summary>
/// Internal helper function to handle cartridges in inventory if any of them exist.
/// Helper function to handle cartridges in inventory if any of them exist.
/// </summary>
protected void HandleCartridges(List<Item> items, InventoryMoveRequestData request)
/// <param name="items"></param>
/// <param name="request"></param>
protected void HandleCartridgeMove(List<Item> items, InventoryMoveRequestData request)
{
// Not moving item into a cartridge slot, skip
if (request.To.Container != "cartridges")
@@ -1352,9 +1356,11 @@ public class InventoryHelper(
/// </summary>
/// <param name="itemTpl">Container being opened</param>
/// <returns>Reward details</returns>
public RewardDetails GetRandomLootContainerRewardDetails(string itemTpl)
public RewardDetails? GetRandomLootContainerRewardDetails(MongoId itemTpl)
{
return _inventoryConfig.RandomLootContainers[itemTpl];
_inventoryConfig.RandomLootContainers.TryGetValue(itemTpl, out var result);
return result;
}
/// <summary>
@@ -1369,7 +1375,7 @@ public class InventoryHelper(
public void ValidateInventoryUsesMongoIds(List<Item> itemsToValidate)
{
var errors = itemsToValidate
.Where(item => !_hashUtil.IsValidMongoId(item.Id))
.Where(item => !item.Id.IsValidMongoId())
.Select(item => $"Id: {item.Id} - tpl: {item.Template}")
.ToList();
foreach (var message in errors)
@@ -1381,40 +1387,13 @@ public class InventoryHelper(
"This profile is not compatible with SPT, See above for a list of incompatible IDs that is not compatible. Loading of SPT has been halted, use another profile or create a new one"
);
}
/// <summary>
/// Does the provided item have a root item with the provided id
/// </summary>
/// <param name="pmcData">Profile with items</param>
/// <param name="item">Item to check</param>
/// <param name="rootId">Root item id to check for</param>
/// <returns>True when item has rootId, false when not</returns>
public bool DoesItemHaveRootId(PmcData pmcData, Item item, string rootId)
{
var currentItem = item;
while (currentItem is not null)
{
// If we've found the equipment root ID, return true
if (currentItem.Id == rootId)
{
return true;
}
// Otherwise get the parent item
currentItem = pmcData.Inventory.Items.FirstOrDefault(item =>
item.Id == currentItem.ParentId
);
}
return false;
}
}
public class InventoryItemHash
{
[JsonPropertyName("byItemId")]
public Dictionary<string, Item> ByItemId { get; set; }
public Dictionary<MongoId, Item> ByItemId { get; set; }
[JsonPropertyName("byParentId")]
public Dictionary<string, HashSet<Item>> ByParentId { get; set; }
public Dictionary<MongoId, HashSet<Item>> ByParentId { get; set; }
}
@@ -28,7 +28,7 @@ public class ItemHelper(
ICloner _cloner
)
{
protected static readonly FrozenSet<string> _defaultInvalidBaseTypes =
protected static readonly FrozenSet<MongoId> _defaultInvalidBaseTypes =
[
BaseClasses.LOOT_CONTAINER,
BaseClasses.MOB_CONTAINER,
@@ -57,7 +57,7 @@ public class ItemHelper(
nameof(EquipmentSlots.Scabbard),
];
protected static readonly FrozenSet<string> _dogTagTpls =
protected static readonly FrozenSet<MongoId> _dogTagTpls =
[
ItemTpl.BARTER_DOGTAG_BEAR,
ItemTpl.BARTER_DOGTAG_BEAR_EOD,
@@ -97,7 +97,7 @@ public class ItemHelper(
"right_side_plate",
];
protected static readonly FrozenSet<string> _armorSlotsThatCanHoldMods =
protected static readonly FrozenSet<MongoId> _armorSlotsThatCanHoldMods =
[
BaseClasses.HEADWEAR,
BaseClasses.VEST,
@@ -108,21 +108,21 @@ public class ItemHelper(
/// Does the provided pool of items contain the desired item
/// </summary>
/// <param name="itemPool">Item collection to check</param>
/// <param name="item">Item to look for</param>
/// <param name="itemTpl">Item to look for</param>
/// <param name="slotId">OPTIONAL - slotId of desired item</param>
/// <returns>True if pool contains item</returns>
public bool HasItemWithTpl(IEnumerable<Item> itemPool, string item, string slotId = "")
public bool HasItemWithTpl(IEnumerable<Item> itemPool, MongoId itemTpl, string slotId = "")
{
// Filter the pool by slotId if provided
var filteredPool = string.IsNullOrEmpty(slotId)
? itemPool
: itemPool.Where(item =>
item.SlotId?.StartsWith(slotId, StringComparison.OrdinalIgnoreCase) ?? false
: itemPool.Where(itemInPool =>
itemInPool.SlotId?.StartsWith(slotId, StringComparison.OrdinalIgnoreCase) ?? false
);
// Check if any item in the filtered pool matches the provided item
return filteredPool.Any(poolItem =>
string.Equals(poolItem.Template, item, StringComparison.OrdinalIgnoreCase)
string.Equals(poolItem.Template, itemTpl, StringComparison.OrdinalIgnoreCase)
);
}
@@ -133,7 +133,7 @@ public class ItemHelper(
/// <param name="tpl">Item tpl to find</param>
/// <param name="slotId">OPTIONAL - slotId of desired item</param>
/// <returns>Item or null if no item found</returns>
public Item GetItemFromPoolByTpl(IEnumerable<Item> itemPool, string tpl, string slotId = "")
public Item GetItemFromPoolByTpl(IEnumerable<Item> itemPool, MongoId tpl, string slotId = "")
{
// Filter the pool by slotId if provided
var filteredPool = string.IsNullOrEmpty(slotId)
@@ -143,7 +143,7 @@ public class ItemHelper(
);
// Check if any item in the filtered pool matches the provided item
return filteredPool.FirstOrDefault(poolItem => poolItem.Template.Equals(tpl));
return filteredPool.FirstOrDefault(poolItem => poolItem.Template == tpl);
}
/// <summary>
@@ -283,7 +283,7 @@ public class ItemHelper(
/// <param name="tpl">Template id to check</param>
/// <param name="invalidBaseTypes">OPTIONAL - Base types deemed invalid</param>
/// <returns>true for items that may be in player possession and not quest items</returns>
public bool IsValidItem(string tpl, ICollection<string>? invalidBaseTypes = null)
public bool IsValidItem(MongoId tpl, ICollection<MongoId>? invalidBaseTypes = null)
{
var baseTypes = invalidBaseTypes ?? _defaultInvalidBaseTypes;
var itemDetails = GetItem(tpl);
@@ -306,7 +306,7 @@ public class ItemHelper(
/// <param name="tpl">Item template id to check</param>
/// <param name="baseClassTpl">Baseclass to check for</param>
/// <returns>is the tpl a descendant</returns>
public bool IsOfBaseclass(MongoId tpl, string baseClassTpl)
public bool IsOfBaseclass(MongoId tpl, MongoId baseClassTpl)
{
return _itemBaseClassService.ItemHasBaseClass(tpl, [baseClassTpl]);
}
@@ -317,29 +317,11 @@ public class ItemHelper(
/// <param name="tpl">Item to check base classes of</param>
/// <param name="baseClassTpls">Base classes to check for</param>
/// <returns>True if any supplied base classes match</returns>
public bool IsOfBaseclasses(string tpl, ICollection<string> baseClassTpls)
public bool IsOfBaseclasses(MongoId tpl, ICollection<MongoId> baseClassTpls)
{
return _itemBaseClassService.ItemHasBaseClass(tpl, baseClassTpls);
}
/// <summary>
/// Temporary until we have better MongoId handling
/// </summary>
/// <param name="tpl"></param>
/// <param name="baseClassTpls"></param>
/// <returns></returns>
public bool IsOfBaseclasses(string tpl, ICollection<MongoId> baseClassTpls)
{
List<string> MongoList = [];
foreach (var baseTpl in baseClassTpls)
{
MongoList.Add(baseTpl);
}
return _itemBaseClassService.ItemHasBaseClass(tpl, MongoList);
}
/// <summary>
/// Does the provided item have the chance to require soft armor inserts
/// Only applies to helmets/vest/armors
@@ -347,7 +329,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Tpl to check</param>
/// <returns>Does item have the possibility ot need soft inserts</returns>
public bool ArmorItemCanHoldMods(string itemTpl)
public bool ArmorItemCanHoldMods(MongoId itemTpl)
{
return IsOfBaseclasses(itemTpl, _armorSlotsThatCanHoldMods);
}
@@ -357,7 +339,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Armor item</param>
/// <returns>True if item needs some kind of insert</returns>
public bool ArmorItemHasRemovableOrSoftInsertSlots(string itemTpl)
public bool ArmorItemHasRemovableOrSoftInsertSlots(MongoId itemTpl)
{
if (!ArmorItemCanHoldMods(itemTpl))
{
@@ -372,12 +354,12 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Item tpl to check for plate support</param>
/// <returns>True when armor can hold plates</returns>
public bool ArmorItemHasRemovablePlateSlots(string itemTpl)
public bool ArmorItemHasRemovablePlateSlots(MongoId itemTpl)
{
var itemTemplate = GetItem(itemTpl);
return itemTemplate.Value.Properties.Slots.Any(slot =>
_removablePlateSlotIds.Contains(slot.Name.ToLower())
_removablePlateSlotIds.Contains(slot.Name.ToLowerInvariant())
);
}
@@ -386,7 +368,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Item tpl to check</param>
/// <returns>True if it needs armor inserts</returns>
public bool ItemRequiresSoftInserts(string itemTpl)
public bool ItemRequiresSoftInserts(MongoId itemTpl)
{
// Not a slot that takes soft-inserts
if (!ArmorItemCanHoldMods(itemTpl))
@@ -408,7 +390,11 @@ public class ItemHelper(
}
// Check if item has slots that match soft insert name ids
if (itemDbDetails.Value.Properties.Slots.Any(slot => IsSoftInsertId(slot.Name.ToLower())))
if (
itemDbDetails.Value.Properties.Slots.Any(slot =>
IsSoftInsertId(slot.Name.ToLowerInvariant())
)
)
{
return true;
}
@@ -456,7 +442,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Item to look price up of</param>
/// <returns>Price in roubles</returns>
public double? GetItemPrice(string tpl)
public double? GetItemPrice(MongoId tpl)
{
var handbookPrice = GetStaticItemPrice(tpl);
if (handbookPrice >= 1)
@@ -473,7 +459,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Item to look price up of</param>
/// <returns>Price in roubles</returns>
public double GetItemMaxPrice(string tpl)
public double GetItemMaxPrice(MongoId tpl)
{
var staticPrice = GetStaticItemPrice(tpl);
var dynamicPrice = GetDynamicItemPrice(tpl);
@@ -486,7 +472,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Items tpl id to look up price</param>
/// <returns>Price in roubles (0 if not found)</returns>
public double GetStaticItemPrice(string tpl)
public double GetStaticItemPrice(MongoId tpl)
{
var handbookPrice = _handbookHelper.GetTemplatePrice(tpl);
if (handbookPrice >= 1)
@@ -502,7 +488,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Items tpl id to look up price</param>
/// <returns>Price in roubles (undefined if not found)</returns>
public double? GetDynamicItemPrice(string tpl)
public double? GetDynamicItemPrice(MongoId tpl)
{
if (_databaseService.GetPrices().TryGetValue(tpl, out var price))
{
@@ -526,7 +512,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">template id to look up</param>
/// <returns>KvP, key = bool, value = template item object</returns>
public KeyValuePair<bool, TemplateItem?> GetItem(string itemTpl)
public KeyValuePair<bool, TemplateItem?> GetItem(MongoId itemTpl)
{
// -> Gets item from <input: _tpl>
if (_databaseService.GetItems().TryGetValue(itemTpl, out var item))
@@ -542,7 +528,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Template id of the item to check</param>
/// <returns>True if the item has slots</returns>
public bool ItemHasSlots(string itemTpl)
public bool ItemHasSlots(MongoId itemTpl)
{
if (_databaseService.GetItems().TryGetValue(itemTpl, out var item))
{
@@ -557,7 +543,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Id of the item to check</param>
/// <returns>true if the item is in the database</returns>
public bool IsItemInDb(string itemTpl)
public bool IsItemInDb(MongoId itemTpl)
{
return _databaseService.GetItems().ContainsKey(itemTpl);
}
@@ -731,7 +717,7 @@ public class ItemHelper(
/// <param name="itemIdToFind">Template id of item to check for</param>
/// <param name="assort">List of items to check in</param>
/// <returns>List of children of requested item</returns>
public List<Item> FindAndReturnChildrenByAssort(string itemIdToFind, List<Item> assort)
public List<Item> FindAndReturnChildrenByAssort(MongoId itemIdToFind, List<Item> assort)
{
List<Item> list = [];
foreach (var itemFromAssort in assort)
@@ -762,7 +748,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Template id to check.</param>
/// <returns>True if it is a dogtag.</returns>
public bool IsDogtag(string tpl)
public bool IsDogtag(MongoId tpl)
{
return _dogTagTpls.Contains(tpl);
}
@@ -772,7 +758,7 @@ public class ItemHelper(
/// </summary>
/// <param name="tpl">Item to check.</param>
/// <returns>True if it can be stacked.</returns>
public bool? IsItemTplStackable(string tpl)
public bool? IsItemTplStackable(MongoId tpl)
{
if (!_databaseService.GetItems().TryGetValue(tpl, out var item))
{
@@ -1192,7 +1178,7 @@ public class ItemHelper(
/// <param name="tpl">Items tpl to check parents of</param>
/// <param name="tplsToCheck">Tpl values to check if parents of item match</param>
/// <returns>bool Match found</returns>
public bool DoesItemOrParentsIdMatch(string tpl, List<string> tplsToCheck)
public bool DoesItemOrParentsIdMatch(MongoId tpl, List<MongoId> tplsToCheck)
{
var itemDetails = GetItem(tpl);
var itemExists = itemDetails.Key;
@@ -1277,7 +1263,7 @@ public class ItemHelper(
/// <param name="itemId">The unique identifier of the item for which to find the main parent.</param>
/// <param name="itemsMap">A Dictionary containing item IDs mapped to their corresponding Item objects for quick lookup.</param>
/// <returns>The Item object representing the top-most parent of the given item, or null if no such parent exists.</returns>
public Item? GetAttachmentMainParent(string itemId, Dictionary<MongoId, Item> itemsMap)
public Item? GetAttachmentMainParent(MongoId itemId, Dictionary<MongoId, Item> itemsMap)
{
var currentItem = itemsMap.FirstOrDefault(x => x.Key == itemId).Value;
@@ -1323,7 +1309,7 @@ public class ItemHelper(
/// <param name="itemId">The unique identifier of the item for which to find the equipment parent.</param>
/// <param name="itemsMap">A Dictionary containing item IDs mapped to their corresponding Item objects for quick lookup.</param>
/// <returns>The Item object representing the equipment parent of the given item, or `null` if no such parent exists</returns>
public Item? GetEquipmentParent(string itemId, Dictionary<string, Item> itemsMap)
public Item? GetEquipmentParent(MongoId itemId, Dictionary<MongoId, Item> itemsMap)
{
var currentItem = itemsMap.GetValueOrDefault(itemId);
@@ -1345,11 +1331,9 @@ public class ItemHelper(
/// <param name="items">Item with children</param>
/// <param name="rootItemId">The base items root id</param>
/// <returns>ItemSize object (width and height)</returns>
public ItemSize GetItemSize(ICollection<Item> items, string rootItemId)
public ItemSize GetItemSize(ICollection<Item> items, MongoId rootItemId)
{
var rootTemplate = GetItem(
items.Where(x => x.Id.Equals(rootItemId)).ToList()[0].Template
).Value;
var rootTemplate = GetItem(items.FirstOrDefault(x => x.Id == rootItemId).Template).Value;
var width = rootTemplate.Properties.Width;
var height = rootTemplate.Properties.Height;
@@ -1364,9 +1348,9 @@ public class ItemHelper(
var forcedRight = 0;
var children = items.FindAndReturnChildrenAsItems(rootItemId);
foreach (var ci in children)
foreach (var child in children)
{
var itemTemplate = GetItem(ci.Template).Value;
var itemTemplate = GetItem(child.Template).Value;
// Calculating child ExtraSize
if (itemTemplate.Properties.ExtraSizeForceAdd ?? false)
@@ -1747,7 +1731,7 @@ public class ItemHelper(
/// </summary>
/// <param name="itemTpl">Tpl of item to get name of</param>
/// <returns>Full name, short name if not found</returns>
public string GetItemName(string itemTpl)
public string GetItemName(MongoId itemTpl)
{
var localeDb = _localeService.GetLocaleDb();
var result = localeDb[$"{itemTpl} Name"];
@@ -1802,9 +1786,9 @@ public class ItemHelper(
if (modSpawnChanceDict is not null && !(slot.Required ?? false))
{
// only roll chance to not include mod if dict exists and has value for this mod type (e.g. front_plate)
if (modSpawnChanceDict.ContainsKey(slot.Name.ToLower()))
if (modSpawnChanceDict.ContainsKey(slot.Name.ToLowerInvariant()))
{
if (!_randomUtil.GetChance100(modSpawnChanceDict[slot.Name.ToLower()]))
if (!_randomUtil.GetChance100(modSpawnChanceDict[slot.Name.ToLowerInvariant()]))
{
continue;
}
@@ -1905,7 +1889,7 @@ public class ItemHelper(
/// <returns>True if it is a slot that holds a removable plate</returns>
public bool IsRemovablePlateSlot(string slotName)
{
return GetRemovablePlateSlotIds().Contains(slotName.ToLower());
return GetRemovablePlateSlotIds().Contains(slotName.ToLowerInvariant());
}
// Get a list of slot names that hold removable plates
@@ -2023,7 +2007,7 @@ public class ItemHelper(
// Return all tpls from Money enum
// Returns string tpls
public List<string> GetMoneyTpls()
public List<MongoId> GetMoneyTpls()
{
return [Money.ROUBLES, Money.DOLLARS, Money.EUROS, Money.GP];
}
@@ -2042,7 +2026,7 @@ public class ItemHelper(
);
}
public string? GetItemBaseType(string tpl, bool rootOnly = true)
public string? GetItemBaseType(MongoId tpl, bool rootOnly = true)
{
var result = GetItem(tpl);
if (!result.Key)
@@ -2077,7 +2061,7 @@ public class ItemHelper(
/// Get a 2D grid of a container's item slots
/// </summary>
/// <param name="containerTpl">Tpl id of the container</param>
public int[][] GetContainerMapping(string containerTpl)
public int[,] GetContainerMapping(string containerTpl)
{
// Get template from db
var containerTemplate = GetItem(containerTpl).Value;
@@ -2086,25 +2070,18 @@ public class ItemHelper(
var height = containerTemplate.Properties.Grids[0].Props.CellsV;
var width = containerTemplate.Properties.Grids[0].Props.CellsH;
return GetBlankContainerMap(height.Value, width.Value);
return GetBlankContainerMap(width.Value, height.Value);
}
/// <summary>
/// Get a blank two-dimensional representation of a container
/// </summary>
/// <param name="containerY">Horizontal size of container</param>
/// <param name="containerX">Vertical size of container</param>
/// <param name="horizontalSizeX">Width of container (columns)</param>
/// <param name="verticalSizeY">Height of container (rows)</param>
/// <returns>Two-dimensional representation of container</returns>
public int[][] GetBlankContainerMap(int containerY, int containerX)
public int[,] GetBlankContainerMap(int horizontalSizeX, int verticalSizeY)
{
//var x = new int[containerY][];
//for (int i = 0; i < containerY; i++)
//{
// x[i] = new int[containerH];
//}
//return x;
return Enumerable.Range(0, containerY).Select(_ => new int[containerX]).ToArray();
// Rows / Columns
return new int[verticalSizeY, horizontalSizeX];
}
}
@@ -5,17 +5,8 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class ModHelper
public class ModHelper(FileUtil fileUtil, JsonUtil jsonUtil)
{
private readonly FileUtil _fileUtil;
private readonly JsonUtil _jsonUtil;
public ModHelper(FileUtil fileUtil, JsonUtil jsonUtil)
{
_fileUtil = fileUtil;
_jsonUtil = jsonUtil;
}
public string GetAbsolutePathToModFolder(Assembly modAssembly)
{
// The full path to the mod folder
@@ -25,15 +16,15 @@ public class ModHelper
public string GetRawFileData(string pathToFile, string fileName)
{
// Read the content of the config file as a string
return _fileUtil.ReadFile(Path.Combine(pathToFile, fileName));
return fileUtil.ReadFile(Path.Combine(pathToFile, fileName));
}
public T GetJsonDataFromFile<T>(string pathToFile, string fileName)
{
// Read the content of the config file as a string
var rawContent = _fileUtil.ReadFile(Path.Combine(pathToFile, fileName));
var rawContent = fileUtil.ReadFile(Path.Combine(pathToFile, fileName));
// Take the string above and deserialise it into a file with a type (defined between the diamond brackets)
return _jsonUtil.Deserialize<T>(rawContent);
return jsonUtil.Deserialize<T>(rawContent);
}
}
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Eft.Ws;
using SPTarkov.Server.Core.Models.Enums;
@@ -15,7 +16,6 @@ namespace SPTarkov.Server.Core.Helpers;
public class NotificationSendHelper(
ISptLogger<NotificationSendHelper> _logger,
SptWebSocketConnectionHandler _sptWebSocketConnectionHandler,
HashUtil _hashUtil,
SaveServer _saveServer,
NotificationService _notificationService,
TimeUtil _timeUtil,
@@ -76,7 +76,7 @@ public class NotificationSendHelper(
dialog.New += 1;
var message = new Message
{
Id = _hashUtil.Generate(),
Id = new MongoId(),
UserId = dialog.Id,
MessageType = messageType,
DateTime = _timeUtil.GetTimeStamp(),
@@ -1,12 +1,12 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Eft.Ws;
using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Helpers;
[Injectable(InjectionType.Singleton)]
public class NotifierHelper(HttpServerHelper httpServerHelper, HashUtil hashUtil)
public class NotifierHelper(HttpServerHelper httpServerHelper)
{
protected static readonly WsPing ping = new();
@@ -57,7 +57,7 @@ public class NotifierHelper(HttpServerHelper httpServerHelper, HashUtil hashUtil
return new WsRagfairNewRating
{
EventType = NotificationEventType.RagfairNewRating,
EventIdentifier = hashUtil.Generate(),
EventIdentifier = new MongoId(),
Rating = rating,
IsRatingGrowing = isGrowing,
};
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Servers;
@@ -10,7 +11,7 @@ public class PaymentHelper(ConfigServer configServer)
{
protected bool _addedCustomMoney;
protected readonly InventoryConfig _inventoryConfig = configServer.GetConfig<InventoryConfig>();
protected readonly HashSet<string> _moneyTpls =
protected readonly HashSet<MongoId> _moneyTpls =
[
Money.DOLLARS,
Money.EUROS,
@@ -23,7 +24,7 @@ public class PaymentHelper(ConfigServer configServer)
/// </summary>
/// <param name="tpl">Item Tpl to check</param>
/// <returns></returns>
public bool IsMoneyTpl(string tpl)
public bool IsMoneyTpl(MongoId tpl)
{
// Add custom currency first time this method is accessed
if (!_addedCustomMoney)
@@ -17,9 +17,9 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
/// <summary>
/// Preset cache - key = item tpl, value = preset ids
/// </summary>
protected Dictionary<string, PresetCacheDetails> _lookup = new();
protected Dictionary<MongoId, PresetCacheDetails> _lookup = new();
public void HydratePresetStore(Dictionary<string, PresetCacheDetails> input)
public void HydratePresetStore(Dictionary<MongoId, PresetCacheDetails> input)
{
_lookup = input;
}
@@ -113,7 +113,7 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
* @param baseClass The BaseClasses enum to check against
* @returns True if the preset is of the given base class, false otherwise
*/
public bool IsPresetBaseClass(string id, string baseClass)
public bool IsPresetBaseClass(string id, MongoId baseClass)
{
return IsPreset(id) && _itemHelper.IsOfBaseclass(GetPreset(id).Encyclopedia, baseClass);
}
@@ -123,7 +123,7 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
/// </summary>
/// <param name="templateId">Tpl id to check</param>
/// <returns>True if preset exists for tpl</returns>
public bool HasPreset(string templateId)
public bool HasPreset(MongoId templateId)
{
return _lookup.ContainsKey(templateId);
}
@@ -169,7 +169,7 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
/// </summary>
/// <param name="templateId">Items tpl to get preset for</param>
/// <returns>null if no default preset, otherwise Preset</returns>
public Preset? GetDefaultPreset(string templateId)
public Preset? GetDefaultPreset(MongoId templateId)
{
// look in main cache for presets for this tpl
if (!_lookup.TryGetValue(templateId, out var presetDetails))
@@ -202,7 +202,7 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
/// </summary>
/// <param name="presetId">Preset id to look up</param>
/// <returns>tpl mongoid</returns>
public string GetBaseItemTpl(string presetId)
public MongoId GetBaseItemTpl(MongoId presetId)
{
if (!_databaseService.GetGlobals().ItemPresets.TryGetValue(presetId, out var preset))
{
@@ -225,7 +225,7 @@ public class PresetHelper(DatabaseService _databaseService, ItemHelper _itemHelp
/// </summary>
/// <param name="tpl">The item template to get the price of</param>
/// <returns>The price of the given item preset, or base item if no preset exists</returns>
public double GetDefaultPresetOrItemPrice(string tpl)
public double GetDefaultPresetOrItemPrice(MongoId tpl)
{
// Get default preset if it exists
var defaultPreset = GetDefaultPreset(tpl);
@@ -123,8 +123,8 @@ public class ProfileHelper(
&& !StringsMatch(p.ProfileInfo.ProfileId, sessionID)
&& // SessionIds dont match
StringsMatch(
p.CharacterData.PmcData.Info.LowerNickname.ToLower(),
nicknameRequest.Nickname.ToLower()
p.CharacterData.PmcData.Info.LowerNickname.ToLowerInvariant(),
nicknameRequest.Nickname.ToLowerInvariant()
)
); // Nicknames do
}
@@ -564,7 +564,7 @@ public class ProfileHelper(
public bool IsDeveloperAccount(string sessionID)
{
return GetFullProfile(sessionID)
?.ProfileInfo?.Edition?.ToLower()
?.ProfileInfo?.Edition?.ToLowerInvariant()
.StartsWith("spt developer") ?? false;
}
@@ -602,24 +602,6 @@ public class ProfileHelper(
}
}
/// <summary>
/// Iterate over all bonuses and sum up all bonuses of desired type in provided profile
/// </summary>
/// <param name="pmcProfile">Player profile</param>
/// <param name="desiredBonus">Bonus to sum up</param>
/// <returns>Summed bonus value or 0 if no bonus found</returns>
public double GetBonusValueFromProfile(PmcData pmcProfile, BonusType desiredBonus)
{
var bonuses = pmcProfile?.Bonuses?.Where(b => b.Type == desiredBonus);
if (bonuses is null || !bonuses.Any())
{
return 0;
}
// Sum all bonuses found above
return bonuses?.Sum(bonus => bonus?.Value ?? 0) ?? 0;
}
public bool HasAccessToRepeatableFreeRefreshSystem(PmcData pmcProfile)
{
return _gameEditionsWithFreeRefresh.Contains(pmcProfile.Info.GameVersion);
@@ -2,6 +2,7 @@ using System.Globalization;
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.ItemEvent;
@@ -57,19 +58,6 @@ public class QuestHelper(
}
}
/// <summary>
/// Get status of a quest in player profile by its id
/// </summary>
/// <param name="pmcData">Profile to search</param>
/// <param name="questId">Quest id to look up</param>
/// <returns>QuestStatus enum</returns>
public QuestStatusEnum GetQuestStatus(PmcData pmcData, string questId)
{
var quest = pmcData.Quests?.FirstOrDefault(q => q.QId == questId);
return quest?.Status ?? QuestStatusEnum.Locked;
}
/// <summary>
/// returns true if the level condition is satisfied
/// </summary>
@@ -636,7 +624,7 @@ public class QuestHelper(
/// <param name="output">ItemEvent router response</param>
public void ChangeItemStack(
PmcData pmcData,
string itemId,
MongoId itemId,
int newStackSize,
string sessionID,
ItemEventRouterResponse output
@@ -666,7 +654,9 @@ public class QuestHelper(
{
// this case is probably dead Code right now, since the only calling function
// checks explicitly for Value > 0.
output.ProfileChanges[sessionID].Items.DeletedItems.Add(new Item { Id = itemId });
output
.ProfileChanges[sessionID]
.Items.DeletedItems.Add(new DeletedItem { Id = itemId });
pmcData.Inventory.Items.RemoveAt(inventoryItemIndex);
}
}
@@ -965,7 +955,7 @@ public class QuestHelper(
/// <param name="pmcData">Profile to update</param>
/// <param name="newQuestState">New state the quest should be in</param>
/// <param name="questId">Id of the quest to alter the status of</param>
public void UpdateQuestState(PmcData pmcData, QuestStatusEnum newQuestState, string questId)
protected void UpdateQuestState(PmcData pmcData, QuestStatusEnum newQuestState, string questId)
{
// Find quest in profile, update status to desired status
var questToUpdate = pmcData.Quests.FirstOrDefault(quest => quest.QId == questId);
@@ -1021,8 +1011,8 @@ public class QuestHelper(
/// <param name="allQuests">All quests to check</param>
/// <returns>quest id with 'FindItem' condition id</returns>
public Dictionary<string, string> GetFindItemConditionByQuestItem(
string itemTpl,
string[] questIds,
MongoId itemTpl,
MongoId[] questIds,
List<Quest> allQuests
)
{
@@ -1604,12 +1594,12 @@ public class QuestHelper(
);
}
/**
* Look for newly available quests after completing a quest with a requirement to wait x minutes (time-locked) before being available and add data to profile
* @param pmcData Player profile to update
* @param quests Quests to look for wait conditions in
* @param completedQuestId Quest just completed
*/
/// <summary>
/// Look for newly available quests after completing a quest with a requirement to wait x minutes (time-locked) before being available and add data to profile
/// </summary>
/// <param name="pmcData">Player profile to update</param>
/// <param name="quests">Quests to look for wait conditions in</param>
/// <param name="completedQuestId">Quest just completed</param>
protected void AddTimeLockedQuestsToProfile(
PmcData pmcData,
List<Quest> quests,
@@ -15,7 +15,6 @@ public class RagfairHelper(
TraderAssortHelper traderAssortHelper,
DatabaseService databaseService,
HandbookHelper handbookHelper,
ItemHelper itemHelper,
RagfairLinkedItemService ragfairLinkedItemService,
ConfigServer configServer,
ICloner cloner
@@ -1,6 +1,7 @@
using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.ItemEvent;
@@ -20,7 +21,6 @@ namespace SPTarkov.Server.Core.Helpers;
public class RagfairOfferHelper(
ISptLogger<RagfairOfferHelper> _logger,
TimeUtil _timeUtil,
HashUtil _hashUtil,
BotHelper _botHelper,
RagfairSortHelper _ragfairSortHelper,
PresetHelper _presetHelper,
@@ -112,7 +112,7 @@ public class RagfairOfferHelper(
protected void CheckAndLockOfferFromPlayerTieredFlea(
TieredFlea tieredFlea,
RagfairOffer offer,
List<string> tieredFleaLimitTypes,
List<MongoId> tieredFleaLimitTypes,
int playerLevel
)
{
@@ -819,7 +819,7 @@ public class RagfairOfferHelper(
// Create an item template item
var requestedItem = new Item
{
Id = _hashUtil.Generate(),
Id = new MongoId(),
Template = requirement.Template,
Upd = new Upd { StackObjectsCount = requirement.Count * boughtAmount },
};
@@ -881,7 +881,7 @@ public class RagfairOfferHelper(
* @param boughtAmount How many were purchased
* @returns Localised message text
*/
protected string GetLocalisedOfferSoldMessage(string itemTpl, int boughtAmount)
protected string GetLocalisedOfferSoldMessage(MongoId itemTpl, int boughtAmount)
{
// Generate a message to inform that item was sold
var globalLocales = _localeService.GetLocaleDb();
@@ -896,7 +896,7 @@ public class RagfairOfferHelper(
}
// Used to replace tokens in sold message sent to player
var messageKey = $"{itemTpl} Name";
var messageKey = $"{itemTpl.ToString()} Name";
var hasKey = globalLocales.TryGetValue(messageKey, out var value);
var tplVars = new SystemData
@@ -1,5 +1,6 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
@@ -18,7 +19,6 @@ public class RagfairServerHelper(
TimeUtil timeUtil,
DatabaseService databaseService,
ItemHelper itemHelper,
TraderHelper traderHelper,
WeightedRandomHelper weightedRandomHelper,
MailSendService mailSendService,
ServerLocalisationService localisationService,
@@ -99,7 +99,7 @@ public class RagfairServerHelper(
* @param itemTemplateId Item tpl to check is blacklisted
* @returns True if its blacklisted
*/
protected bool IsItemOnCustomFleaBlacklist(string itemTemplateId)
protected bool IsItemOnCustomFleaBlacklist(MongoId itemTemplateId)
{
return ragfairConfig.Dynamic.Blacklist.Custom.Contains(itemTemplateId);
}
@@ -146,7 +146,7 @@ public class RagfairServerHelper(
);
}
public int CalculateDynamicStackCount(string tplId, bool isPreset)
public int CalculateDynamicStackCount(MongoId tplId, bool isPreset)
{
var config = ragfairConfig.Dynamic;
@@ -6,7 +6,6 @@ using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Helpers;
@@ -16,7 +15,6 @@ public class RepeatableQuestHelper(
ISptLogger<RepeatableQuestHelper> logger,
DatabaseService databaseService,
ServerLocalisationService serverLocalisationService,
HashUtil hashUtil,
ICloner cloner,
ConfigServer configServer
)
@@ -88,7 +86,7 @@ public class RepeatableQuestHelper(
return null;
}
quest.Id = hashUtil.Generate();
quest.Id = new MongoId();
quest.TraderId = traderId;
return quest;
@@ -202,7 +200,7 @@ public class RepeatableQuestHelper(
return null;
}
questData.QuestStatus.Id = hashUtil.Generate();
questData.QuestStatus.Id = new MongoId();
questData.QuestStatus.Uid = sessionId; // Needs to match user id
questData.QuestStatus.QId = questData.Id; // Needs to match quest id
@@ -1,30 +0,0 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class SecureContainerHelper(ItemHelper _itemHelper)
{
/// <summary>
/// Get a list of the item IDs (NOT tpls) inside a secure container
/// </summary>
/// <param name="items">Inventory items to look for secure container in</param>
/// <returns>List of ids</returns>
public List<string> GetSecureContainerItems(List<Item> items)
{
var secureContainer = items.First(x => x.SlotId == "SecuredContainer");
// No container found, drop out
if (secureContainer is null)
{
return [];
}
var itemsInSecureContainer = items.FindAndReturnChildrenByItems(secureContainer.Id);
// Return all items returned and exclude the secure container item itself
return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToList();
}
}
@@ -1,6 +1,7 @@
using System.Text.RegularExpressions;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Inventory;
@@ -19,7 +20,6 @@ namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class TradeHelper(
ISptLogger<TradeHelper> _logger,
DatabaseService _databaseService,
TraderHelper _traderHelper,
ItemHelper _itemHelper,
QuestHelper _questHelper,
@@ -415,7 +415,7 @@ public record PurchaseDetails
public record PurchaseItems
{
public string ItemId { get; set; }
public MongoId ItemId { get; set; }
public double Count { get; set; }
}
@@ -526,7 +526,7 @@ public class TraderHelper(
/// </summary>
/// <param name="tpl">Item to look up highest price for</param>
/// <returns>highest rouble cost for item</returns>
public double GetHighestTraderPriceRouble(string tpl)
public double GetHighestTraderPriceRouble(MongoId tpl)
{
if (_highestTraderPriceItems is not null)
{
@@ -580,7 +580,7 @@ public class TraderHelper(
/// </summary>
/// <param name="tpl">Item to look up best trader sell-to price</param>
/// <returns>Rouble price</returns>
public double GetHighestSellToTraderPrice(string tpl)
public double GetHighestSellToTraderPrice(MongoId tpl)
{
// Find largest trader price for item
var highestPrice = 1d; // Default price
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Spt.Helper;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
@@ -111,7 +112,7 @@ public class WeightedRandomHelper(
/// Find the greated common divisor of all weights and use it on the passed in dictionary
/// </summary>
/// <param name="weightedDict">Values to reduce</param>
public void ReduceWeightValues(IDictionary<string, double> weightedDict)
public void ReduceWeightValues(IDictionary<MongoId, double> weightedDict)
{
// No values, nothing to reduce
if (weightedDict.Count == 0)
@@ -11,7 +11,7 @@ public record IdWithCount
/// ID of stack to take money from
/// </summary>
[JsonPropertyName("id")]
public string? Id { get; set; }
public MongoId Id { get; set; }
/// <summary>
/// Amount of money to take off player for treatment
@@ -1,39 +1,32 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using SPTarkov.Server.Core.Extensions;
namespace SPTarkov.Server.Core.Models.Common;
public readonly partial struct MongoId : IEquatable<MongoId>
public readonly struct MongoId : IEquatable<MongoId>
{
private readonly string _stringId;
private readonly string? _stringId;
public MongoId(
string id,
// TODO: TEMPORARY REMOVE ME WHEN DONE!!!!
[CallerFilePath] string callerFilePath = "",
[CallerMemberName] string methodName = "",
[CallerLineNumber] int callerLineNumber = 0
)
public MongoId(string? id)
{
// This is temporary, otherwise item buying is broken as when LINQ searches for string id's it's possible null is passed
if (id == null)
// Handle null strings, various id's are null either by BSG or by our own doing with LINQ
if (string.IsNullOrEmpty(id))
{
id = string.Empty;
_stringId = null;
return;
}
if (id.Length != 24)
{
// TODO: Items.json root item has an empty parentId property
Console.WriteLine(
$"Critical MongoId error [{callerFilePath}::{methodName} L{callerLineNumber}]: Incorrect length. id: {id}"
);
Console.WriteLine($"Critical MongoId error: Incorrect length. id: {id}");
}
if (!IsValidMongoId(id))
{
Console.WriteLine(
$"Critical MongoId error [{callerFilePath}::{methodName} L{callerLineNumber}]: Incorrect format. Must be a hexadecimal [a-f0-9] of 24 characters. id: {id}"
$"Critical MongoId error: Incorrect format. Must be a hexadecimal [a-f0-9] of 24 characters. id: {id}"
);
}
@@ -92,7 +85,7 @@ public readonly partial struct MongoId : IEquatable<MongoId>
{
if (other is null)
{
return other == this;
return this == null;
}
return other.Equals(ToString(), StringComparison.InvariantCultureIgnoreCase);
@@ -135,7 +128,7 @@ public readonly partial struct MongoId : IEquatable<MongoId>
public override int GetHashCode()
{
return _stringId.GetHashCode();
return (_stringId ?? string.Empty).GetHashCode();
}
public bool IsEmpty()
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Utils.Json;
namespace SPTarkov.Server.Core.Models.Eft.Common;
@@ -147,7 +148,7 @@ public record StaticForced
public string ContainerId { get; set; }
[JsonPropertyName("itemTpl")]
public string ItemTpl { get; set; }
public MongoId ItemTpl { get; set; }
}
public record StaticContainerData
@@ -291,7 +291,7 @@ public record LocationBase
public long? UnixDateTime { get; set; }
[JsonPropertyName("_Id")]
public string? IdField { get; set; }
public MongoId IdField { get; set; }
[JsonPropertyName("doors")]
public List<object>? Doors { get; set; }
@@ -14,7 +14,7 @@ public record BotBase
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("_id")]
public string? Id { get; set; }
public MongoId? Id { get; set; }
[JsonPropertyName("aid")]
[JsonConverter(typeof(StringToNumberFactoryConverter))]
@@ -28,7 +28,7 @@ public record BotBase
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonPropertyName("savage")]
public string? Savage { get; set; }
public MongoId? Savage { get; set; }
[JsonPropertyName("karmaValue")]
public double? KarmaValue { get; set; }
@@ -53,10 +53,10 @@ public record BotBase
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonPropertyName("Encyclopedia")]
public Dictionary<string, bool>? Encyclopedia { get; set; }
public Dictionary<MongoId, bool>? Encyclopedia { get; set; }
[JsonPropertyName("TaskConditionCounters")]
public Dictionary<string, TaskConditionCounter>? TaskConditionCounters { get; set; }
public Dictionary<MongoId, TaskConditionCounter>? TaskConditionCounters { get; set; }
[JsonPropertyName("InsuredItems")]
public List<InsuredItem>? InsuredItems { get; set; }
@@ -82,7 +82,7 @@ public record BotBase
/// </summary>
[JsonPropertyName("Achievements")]
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
public Dictionary<string, long>? Achievements { get; set; }
public Dictionary<MongoId, long>? Achievements { get; set; }
[JsonPropertyName("RepeatableQuests")]
public List<PmcDataRepeatableQuest>? RepeatableQuests { get; set; }
@@ -105,7 +105,7 @@ public record BotBase
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonPropertyName("WishList")]
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
public DictionaryOrList<string, int>? WishList { get; set; }
public DictionaryOrList<MongoId, int>? WishList { get; set; }
[JsonPropertyName("moneyTransferLimitData")]
public MoneyTransferLimits? MoneyTransferLimitData { get; set; }
@@ -571,7 +571,7 @@ public record DroppedItem
public string? QuestId { get; set; }
public string? ItemId { get; set; }
public MongoId? ItemId { get; set; }
public string? ZoneId { get; set; }
}
@@ -583,7 +583,7 @@ public record FoundInRaidItem
public string? QuestId { get; set; }
public string? ItemId { get; set; }
public MongoId? ItemId { get; set; }
}
public record Victim
@@ -826,7 +826,7 @@ public record Hideout
/// </summary>
public string? Seed { get; set; }
public Dictionary<string, string>? MannequinPoses { get; set; }
public Dictionary<string, MongoId>? MannequinPoses { get; set; }
[JsonPropertyName("sptUpdateLastRunTimestamp")]
public long? SptUpdateLastRunTimestamp { get; set; }
@@ -938,7 +938,7 @@ public record Production // use this instead of productive and scavcase
[JsonPropertyName("sptIsCultistCircle")]
public bool? SptIsCultistCircle { get; set; }
public string? RecipeId { get; set; }
public MongoId RecipeId { get; set; }
}
public record BotHideoutArea
@@ -1094,7 +1094,7 @@ public record Bonus
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("id")]
public MongoId? Id { get; set; }
public MongoId Id { get; set; }
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
@@ -358,7 +358,7 @@ public record GenerationData
/// </summary>
[JsonPropertyName("whitelist")]
[JsonConverter(typeof(ArrayToObjectFactoryConverter))]
public Dictionary<string, double>? Whitelist { get; set; }
public Dictionary<MongoId, double>? Whitelist { get; set; }
}
public record GenerationWeightingItems
@@ -496,15 +496,15 @@ public record ItemPools
[JsonExtensionData]
public Dictionary<string, object>? ExtensionData { get; set; }
public Dictionary<string, double>? Backpack { get; set; }
public Dictionary<MongoId, double>? Backpack { get; set; }
public Dictionary<string, double>? Pockets { get; set; }
public Dictionary<MongoId, double>? Pockets { get; set; }
public Dictionary<string, double>? SecuredContainer { get; set; }
public Dictionary<MongoId, double>? SecuredContainer { get; set; }
public Dictionary<string, double>? SpecialLoot { get; set; }
public Dictionary<MongoId, double>? SpecialLoot { get; set; }
public Dictionary<string, double>? TacticalVest { get; set; }
public Dictionary<MongoId, double>? TacticalVest { get; set; }
}
public record BotDbSkills
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Models.Common;
namespace SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -20,11 +21,11 @@ public record HandbookCategory
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("Id")]
public string? Id { get; set; }
public MongoId Id { get; set; }
[JsonPropertyName("ParentId")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string? ParentId { get; set; }
public MongoId? ParentId { get; set; }
[JsonPropertyName("Icon")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
@@ -43,11 +44,11 @@ public record HandbookItem
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("Id")]
public string? Id { get; set; }
public MongoId Id { get; set; }
[JsonPropertyName("ParentId")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string? ParentId { get; set; }
public MongoId ParentId { get; set; }
[JsonPropertyName("Price")]
public double? Price { get; set; }
@@ -58,19 +58,11 @@ public record HideoutItem
public MongoId _Id
{
get { return Id; }
set
{
if (value == null)
{
return;
}
Id = value;
}
set { Id = value; }
}
[JsonPropertyName("id")]
public required MongoId Id { get; set; }
public MongoId Id { get; set; }
[JsonPropertyName("_tpl")]
public required MongoId Template { get; set; }
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Utils.Json.Converters;
@@ -72,7 +73,7 @@ public record TraderService
public Dictionary<string, ServiceItemCostDetails>? ServiceItemCost { get; set; }
[JsonPropertyName("UniqueItems")]
public List<string>? UniqueItems { get; set; }
public List<MongoId>? UniqueItems { get; set; }
}
public record ServiceRequirements
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Models.Common;
namespace SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -8,7 +9,7 @@ public record LocationsGenerateAllResponse
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("locations")]
public Dictionary<string, LocationBase> Locations { get; set; }
public Dictionary<MongoId, LocationBase> Locations { get; set; }
[JsonPropertyName("paths")]
public List<Path>? Paths { get; set; }
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Utils.Json;
using SPTarkov.Server.Core.Utils.Json.Converters;
@@ -20,7 +21,7 @@ public record Quest
/// _id
/// </summary>
[JsonPropertyName("_id")]
public required string Id { get; set; }
public required MongoId Id { get; set; }
[JsonPropertyName("canShowNotificationsInGame")]
public required bool CanShowNotificationsInGame { get; set; }
@@ -144,7 +145,7 @@ public record QuestStatus
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("qid")]
public required string QId { get; set; }
public required MongoId QId { get; set; }
[JsonPropertyName("startTime")]
public required double StartTime { get; set; }
@@ -189,7 +190,7 @@ public record QuestCondition
public Dictionary<string, object>? ExtensionData { get; set; }
[JsonPropertyName("id")]
public required string Id { get; set; }
public required MongoId Id { get; set; }
[JsonPropertyName("index")]
public int? Index { get; set; }

Some files were not shown because too many files have changed in this diff Show More