This commit is contained in:
Chomp
2025-02-05 10:59:52 +00:00
376 changed files with 5917 additions and 9939 deletions
+54 -55
View File
@@ -3,74 +3,73 @@ using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Servers;
namespace ExampleMods.Mods
namespace ExampleMods.Mods;
[Injectable]
public class EditConfigs
{
[Injectable]
public class EditConfigs
private readonly ConfigServer _configServer;
private readonly BotConfig _botConfig;
private readonly HideoutConfig _hideoutConfig;
private readonly WeatherConfig _weatherConfig;
private readonly AirdropConfig _airdropConfig;
private readonly PmcChatResponse _pmcChatResponseConfig;
private readonly QuestConfig _questConfig;
private readonly PmcConfig _pmcConfig;
// We access configs via ConfigServer
public EditConfigs(
ConfigServer configServer)
{
private readonly ConfigServer _configServer;
private readonly BotConfig _botConfig;
private readonly HideoutConfig _hideoutConfig;
private readonly WeatherConfig _weatherConfig;
private readonly AirdropConfig _airdropConfig;
private readonly PmcChatResponse _pmcChatResponseConfig;
private readonly QuestConfig _questConfig;
private readonly PmcConfig _pmcConfig;
_configServer = configServer;
// We access configs via ConfigServer
public EditConfigs(
ConfigServer configServer)
{
_configServer = configServer;
// We get the bot config by calling GetConfig and passing the configs 'type' within the diamond brackets
_botConfig = _configServer.GetConfig<BotConfig>();
_hideoutConfig = _configServer.GetConfig<HideoutConfig>();
_weatherConfig = _configServer.GetConfig<WeatherConfig>();
_airdropConfig = _configServer.GetConfig<AirdropConfig>();
_pmcChatResponseConfig = _configServer.GetConfig<PmcChatResponse>();
_questConfig = _configServer.GetConfig<QuestConfig>();
_pmcConfig = _configServer.GetConfig<PmcConfig>();
// We get the bot config by calling GetConfig and passing the configs 'type' within the diamond brackets
_botConfig = _configServer.GetConfig<BotConfig>();
_hideoutConfig = _configServer.GetConfig<HideoutConfig>();
_weatherConfig = _configServer.GetConfig<WeatherConfig>();
_airdropConfig = _configServer.GetConfig<AirdropConfig>();
_pmcChatResponseConfig = _configServer.GetConfig<PmcChatResponse>();
_questConfig = _configServer.GetConfig<QuestConfig>();
_pmcConfig = _configServer.GetConfig<PmcConfig>();
Run();
}
Run();
}
public void Run()
{
// Let's edit the weather config to make the season winter
_weatherConfig.OverrideSeason = Season.WINTER;
public void Run()
{
// Let's edit the weather config to make the season winter
_weatherConfig.OverrideSeason = Season.WINTER;
// Let's edit the hideout config to Make all crafts take 60 seconds
_hideoutConfig.OverrideCraftTimeSeconds = 60;
// Let's edit the hideout config to Make all crafts take 60 seconds
_hideoutConfig.OverrideCraftTimeSeconds = 60;
// Let's edit the hideout config to Make all upgrades take 60 seconds
_hideoutConfig.OverrideBuildTimeSeconds = 60;
// Let's edit the hideout config to Make all upgrades take 60 seconds
_hideoutConfig.OverrideBuildTimeSeconds = 60;
// Let's edit the airdrop config to Make weapon/armor drops really common
_airdropConfig.AirdropTypeWeightings[SptAirdropTypeEnum.weaponArmor] = 999;
// Let's edit the airdrop config to Make weapon/armor drops really common
_airdropConfig.AirdropTypeWeightings[SptAirdropTypeEnum.weaponArmor] = 999;
// Let's edit the airdrop config to Make weapon/armor drops always have 3 sealed weapon crates
var weaponCrateMinMax = _airdropConfig.Loot["weaponArmor"].WeaponCrateCount;
weaponCrateMinMax.Min = 3;
weaponCrateMinMax.Max = 3;
// Let's edit the airdrop config to Make weapon/armor drops always have 3 sealed weapon crates
var weaponCrateMinMax = _airdropConfig.Loot["weaponArmor"].WeaponCrateCount;
weaponCrateMinMax.Min = 3;
weaponCrateMinMax.Max = 3;
// Let's make PMCs always mail you when they kill you
_pmcChatResponseConfig.Killer.ResponseChancePercent = 100;
// Let's make PMCs always mail you when they kill you
_pmcChatResponseConfig.Killer.ResponseChancePercent = 100;
// Let's make quest rewards sent to you via mail last for over a week if you have an unheard profile
_questConfig.MailRedeemTimeHours["unheard_edition"] = 168;
// Let's make quest rewards sent to you via mail last for over a week if you have an unheard profile
_questConfig.MailRedeemTimeHours["unheard_edition"] = 168;
// Let's make the interchange bot cap huge
_botConfig.MaxBotCap["interchange"] = 50;
// Let's make the interchange bot cap huge
_botConfig.MaxBotCap["interchange"] = 50;
// Let's disable loot on scavs
_botConfig.DisableLootOnBotTypes.Add("assault");
// Let's disable loot on scavs
_botConfig.DisableLootOnBotTypes.Add("assault");
// Let's make the conversion rate of scavs to pmcs 100% on factory day
var factory4DayConversionSettings = _pmcConfig.ConvertIntoPmcChance["factory4_day"];
var assaultConversionSettings = factory4DayConversionSettings["assault"];
assaultConversionSettings.Min = 100;
assaultConversionSettings.Max = 100;
}
// Let's make the conversion rate of scavs to pmcs 100% on factory day
var factory4DayConversionSettings = _pmcConfig.ConvertIntoPmcChance["factory4_day"];
var assaultConversionSettings = factory4DayConversionSettings["assault"];
assaultConversionSettings.Min = 100;
assaultConversionSettings.Max = 100;
}
}
+174 -185
View File
@@ -4,197 +4,186 @@ using Core.Models.Enums;
using Core.Models.Utils;
using Core.Services;
namespace ExampleMods.Mods
namespace ExampleMods.Mods;
[Injectable]
public class EditDatabaseValues
{
[Injectable]
public class EditDatabaseValues
private readonly DatabaseService _databaseService;
public EditDatabaseValues(
DatabaseService databaseService)
{
private readonly DatabaseService _databaseService;
_databaseService = databaseService;
public EditDatabaseValues(
DatabaseService databaseService)
Run();
}
public void Run()
{
// When SPT starts, it stores all the data found in (SPT_Data\Server\database) in memory
// We can use the '_databaseService' we injected to access this data, this includes files from EFT and SPT
// Lets edit some globals settings to make the game easier
EditGlobals();
// Lets edit the BTR to have the christmas tarcola skin
EditBtr();
// Let's edit the hideout so it's easier to upgrade the lavatory
EditHideout();
// Lets edit the default scav to
EditScavSettings();
// Lets edit Customs
EditCustoms();
}
private void EditGlobals()
{
// Let's edit settings in the GLOBALS file (database/globals.json)
var globals = _databaseService.GetGlobals();
// Let's edit the scav cooldown to be 1 second
globals.Configuration.SavagePlayCooldown = 1;
// Now lets try editing the ragfair unlock level, lets get the ragfair settings first
var ragfairSettings = globals.Configuration.RagFair;
// Lets set the level you need to be to access flea to be 1
ragfairSettings.MinUserLevel = 1;
// Now lets increase the number of offers you can have listed at one time
// The max is stored in a list, different flea ratings give different offer amounts
// We loop over all the settings, setting all of them to be 20
foreach (var offerCountSettings in ragfairSettings.MaxActiveOfferCount) offerCountSettings.Count = 20;
}
private void EditBtr()
{
// BTR setting can be found in the GLOBALS file too
var globals = _databaseService.GetGlobals();
// We get the BTR settings from globals first
var btrSettings = globals.Configuration.BTRSettings;
// Let's get the settings for woods specifically, we use 'tryGetValue' for this, the settings will be stored in 'woodsBtrSettings'
btrSettings.MapsConfigs.TryGetValue("Woods", out var woodsBtrSettings);
// Lets set the BTR to use the christmas skin
woodsBtrSettings.BtrSkin = "Tarcola";
}
private void EditHideout()
{
// Hideout data can be found in (SPT_Data\Server\database\hideout)
var hideout = _databaseService.GetHideout();
// We want the areas, they're stored in a list
var hideoutAreas = hideout.Areas;
// We find the toilet, we use 'firstOrDefault', if we cant find the lavatory, 'lavatoryArea' will be null
var lavatoryArea = hideoutAreas.FirstOrDefault(area => area.Type == HideoutAreas.LAVATORY);
// Now we have the toilet, we can find the requirements to craft, all data is stored by stage
var toiletStages = lavatoryArea.Stages;
// Stages are stored in a dictionary, a dictionary has a 'key' and a 'value'
// In this case, the 'key' is the upgrade stage, e.g. "1", or "2"
// We reference to each stage as a 'stageKvP' this means 'Key value Pair', every key has a value (key = stage number, value = data for that stage)
foreach (var stageKvP in toiletStages)
{
_databaseService = databaseService;
// while we're here, we can make the stages craft really fast (60 seconds)
stageKvP.Value.ConstructionTime = 60;
Run();
}
// Let's get the stage requirements, they're a list
var stageRequirements = stageKvP.Value.Requirements;
public void Run()
{
// When SPT starts, it stores all the data found in (SPT_Data\Server\database) in memory
// We can use the '_databaseService' we injected to access this data, this includes files from EFT and SPT
// Lets edit some globals settings to make the game easier
EditGlobals();
// Lets edit the BTR to have the christmas tarcola skin
EditBtr();
// Let's edit the hideout so it's easier to upgrade the lavatory
EditHideout();
// Lets edit the default scav to
EditScavSettings();
// Lets edit Customs
EditCustoms();
}
private void EditGlobals()
{
// Let's edit settings in the GLOBALS file (database/globals.json)
var globals = _databaseService.GetGlobals();
// Let's edit the scav cooldown to be 1 second
globals.Configuration.SavagePlayCooldown = 1;
// Now lets try editing the ragfair unlock level, lets get the ragfair settings first
var ragfairSettings = globals.Configuration.RagFair;
// Lets set the level you need to be to access flea to be 1
ragfairSettings.MinUserLevel = 1;
// Now lets increase the number of offers you can have listed at one time
// The max is stored in a list, different flea ratings give different offer amounts
// We loop over all the settings, setting all of them to be 20
foreach (var offerCountSettings in ragfairSettings.MaxActiveOfferCount)
{
offerCountSettings.Count = 20;
}
}
private void EditBtr()
{
// BTR setting can be found in the GLOBALS file too
var globals = _databaseService.GetGlobals();
// We get the BTR settings from globals first
var btrSettings = globals.Configuration.BTRSettings;
// Let's get the settings for woods specifically, we use 'tryGetValue' for this, the settings will be stored in 'woodsBtrSettings'
btrSettings.MapsConfigs.TryGetValue("Woods", out var woodsBtrSettings);
// Lets set the BTR to use the christmas skin
woodsBtrSettings.BtrSkin = "Tarcola";
}
private void EditHideout()
{
// Hideout data can be found in (SPT_Data\Server\database\hideout)
Core.Models.Spt.Hideout.Hideout hideout = _databaseService.GetHideout();
// We want the areas, they're stored in a list
List<HideoutArea>? hideoutAreas = hideout.Areas;
// We find the toilet, we use 'firstOrDefault', if we cant find the lavatory, 'lavatoryArea' will be null
HideoutArea? lavatoryArea = hideoutAreas.FirstOrDefault(area => area.Type == HideoutAreas.LAVATORY);
// Now we have the toilet, we can find the requirements to craft, all data is stored by stage
var toiletStages = lavatoryArea.Stages;
// Stages are stored in a dictionary, a dictionary has a 'key' and a 'value'
// In this case, the 'key' is the upgrade stage, e.g. "1", or "2"
// We reference to each stage as a 'stageKvP' this means 'Key value Pair', every key has a value (key = stage number, value = data for that stage)
foreach (var stageKvP in toiletStages)
{
// while we're here, we can make the stages craft really fast (60 seconds)
stageKvP.Value.ConstructionTime = 60;
// Let's get the stage requirements, they're a list
var stageRequirements = stageKvP.Value.Requirements;
// We empty the requirements out, now it can be built straight away
stageRequirements.Clear();
}
}
private void EditScavSettings()
{
var bots = _databaseService.GetBots();
// Same as the above example, we use 'TryGetValue' to get the 'assault' bot (assault is the internal name for scavs)
bots.Types.TryGetValue("assault", out var assaultBot);
// Let's make the chance to get a good backpack really high
assaultBot.BotInventory.Equipment.TryGetValue(EquipmentSlots.Backpack, out Dictionary<string, double> backPacks);
// We access the backpacks dictionary by key directly using square brackets, we use ItemTpl to get the items ID
// Alternately, we could have typed backPacks["59e763f286f7742ee57895da"] and done the same thing, ItemTpl makes it easier to read
backPacks[ItemTpl.BACKPACK_PILGRIM_TOURIST] = 999999;
// Now lets make them always have an M4A1
assaultBot.BotInventory.Equipment.TryGetValue(EquipmentSlots.FirstPrimaryWeapon, out Dictionary<string, double> primaryWeapons);
// We edit the weight value (pick chance) that is already there to be massive, making the item more likely to be picked
primaryWeapons[ItemTpl.ASSAULTRIFLE_COLT_M4A1_556X45_ASSAULT_RIFLE] = 999999;
// Now lets make them always have the first name of Gary
// We start by removing all the existing names
assaultBot.FirstNames.Clear();
// We add the new name Gary, very menacing
assaultBot.FirstNames.Add("Gary");
}
private void EditCustoms()
{
// Let's get all the maps (called locations)
var locations = _databaseService.GetLocations();
// Customs is called 'bigmap' in eft
var customs = locations.Bigmap;
// Lets get the exits and make them all 100% chance to appear
var exits = customs.Base.Exits;
// They're stored as a list so we can loop over them
foreach (var exit in exits)
{
// I can't remember which one is used, you'd assume ChancePVE is used in pve, but this is BSG we're dealing with
// So we set both
exit.Chance = 100;
exit.ChancePVE = 100;
}
// Lets try editing the airdrops on customs to be better
var airdropSettings = customs.Base.AirdropParameters;
// They're stored in an array but there's only one bunch of settings, it means we have to get the first item from the list,
// An alternate way to access the first item is done by using square brackets with the 'index' of the item we want,
// indexes start at 0 so we want to type "[0]" to access the first item in the list,
var actualAirdropSettings = airdropSettings.First();
// Make it spawn 100%
actualAirdropSettings.PlaneAirdropChance = 1; // Number between 0 and 1
// Make it spawn as early as start of raid
actualAirdropSettings.PlaneAirdropStartMin = 1;
// Let's make bosses spawn 100% of the time
// We get all the bosses, they're stored in a list
var bosses = customs.Base.BossLocationSpawn;
// Let's get Reshala, we use "FirstOrDefault" and look for the first boss with the name "bossBully"
var reshala = bosses.FirstOrDefault(boss => boss.BossName == "bossBully");
// Set him to 100%
reshala.BossChance = 100;
// We empty the requirements out, now it can be built straight away
stageRequirements.Clear();
}
}
private void EditScavSettings()
{
var bots = _databaseService.GetBots();
// Same as the above example, we use 'TryGetValue' to get the 'assault' bot (assault is the internal name for scavs)
bots.Types.TryGetValue("assault", out var assaultBot);
// Let's make the chance to get a good backpack really high
assaultBot.BotInventory.Equipment.TryGetValue(EquipmentSlots.Backpack, out var backPacks);
// We access the backpacks dictionary by key directly using square brackets, we use ItemTpl to get the items ID
// Alternately, we could have typed backPacks["59e763f286f7742ee57895da"] and done the same thing, ItemTpl makes it easier to read
backPacks[ItemTpl.BACKPACK_PILGRIM_TOURIST] = 999999;
// Now lets make them always have an M4A1
assaultBot.BotInventory.Equipment.TryGetValue(EquipmentSlots.FirstPrimaryWeapon, out var primaryWeapons);
// We edit the weight value (pick chance) that is already there to be massive, making the item more likely to be picked
primaryWeapons[ItemTpl.ASSAULTRIFLE_COLT_M4A1_556X45_ASSAULT_RIFLE] = 999999;
// Now lets make them always have the first name of Gary
// We start by removing all the existing names
assaultBot.FirstNames.Clear();
// We add the new name Gary, very menacing
assaultBot.FirstNames.Add("Gary");
}
private void EditCustoms()
{
// Let's get all the maps (called locations)
var locations = _databaseService.GetLocations();
// Customs is called 'bigmap' in eft
var customs = locations.Bigmap;
// Lets get the exits and make them all 100% chance to appear
var exits = customs.Base.Exits;
// They're stored as a list so we can loop over them
foreach (var exit in exits)
{
// I can't remember which one is used, you'd assume ChancePVE is used in pve, but this is BSG we're dealing with
// So we set both
exit.Chance = 100;
exit.ChancePVE = 100;
}
// Lets try editing the airdrops on customs to be better
var airdropSettings = customs.Base.AirdropParameters;
// They're stored in an array but there's only one bunch of settings, it means we have to get the first item from the list,
// An alternate way to access the first item is done by using square brackets with the 'index' of the item we want,
// indexes start at 0 so we want to type "[0]" to access the first item in the list,
var actualAirdropSettings = airdropSettings.First();
// Make it spawn 100%
actualAirdropSettings.PlaneAirdropChance = 1; // Number between 0 and 1
// Make it spawn as early as start of raid
actualAirdropSettings.PlaneAirdropStartMin = 1;
// Let's make bosses spawn 100% of the time
// We get all the bosses, they're stored in a list
var bosses = customs.Base.BossLocationSpawn;
// Let's get Reshala, we use "FirstOrDefault" and look for the first boss with the name "bossBully"
var reshala = bosses.FirstOrDefault(boss => boss.BossName == "bossBully");
// Set him to 100%
reshala.BossChance = 100;
}
}
+25 -26
View File
@@ -7,36 +7,35 @@ using SptCommon.Annotations;
using Core.Models.Logging;
using Core.Models.Utils;
namespace ExampleMods.Mods
namespace ExampleMods.Mods;
[Injectable]
public class Logging
{
[Injectable]
public class Logging
private readonly ISptLogger<Logging> _logger;
// Constructor - Inject a 'ISptLogger' with your mods Class in the diamond brackets
public Logging(
ISptLogger<Logging> logger)
{
private readonly ISptLogger<Logging> _logger;
// Save the logger we're injecting into a private variable that is scoped to this class (only this class has access to it)
_logger = logger;
// Constructor - Inject a 'ISptLogger' with your mods Class in the diamond brackets
public Logging(
ISptLogger<Logging> logger)
{
// Save the logger we're injecting into a private variable that is scoped to this class (only this class has access to it)
_logger = logger;
// Not 100% necessary but let's split our code out into a method to make it easier to read
Run();
}
// Not 100% necessary but let's split our code out into a method to make it easier to read
Run();
}
public void Run()
{
// We can access the logger to assigned in the constructor here
_logger.Success("This is a success message");
_logger.Warning("This is a warning message");
_logger.Error("This is an error message");
_logger.Info("This is an info message");
_logger.Critical("this is a critical message");
public void Run()
{
// We can access the logger to assigned in the constructor here
_logger.Success("This is a success message");
_logger.Warning("This is a warning message");
_logger.Error("This is an error message");
_logger.Info("This is an info message");
_logger.Critical("this is a critical message");
// Logging with colors requires you to 'pass' the text color and background color
_logger.LogWithColor("This is a message with custom colors", LogTextColor.Red, LogBackgroundColor.Black);
_logger.Debug("This is a debug message that gets written to the log file, not the console");
}
// Logging with colors requires you to 'pass' the text color and background color
_logger.LogWithColor("This is a message with custom colors", LogTextColor.Red, LogBackgroundColor.Black);
_logger.Debug("This is a debug message that gets written to the log file, not the console");
}
}
@@ -12,7 +12,12 @@ public class WatermarkOverride(
ConfigServer _configServer,
LocalisationService _localisationService,
WatermarkLocale _watermarkLocale
) : Watermark(_logger, _configServer, _localisationService, _watermarkLocale) // was testing overriding with primary constructors, works fine from what i can see
) : Watermark(
_logger,
_configServer,
_localisationService,
_watermarkLocale
) // was testing overriding with primary constructors, works fine from what i can see
{
public override void Initialize()
{
@@ -6,8 +6,7 @@ using Core.Utils;
namespace Core.Callbacks;
[Injectable(InjectableTypeOverride = typeof(AchievementCallbacks))]
public class AchievementCallbacks
(
public class AchievementCallbacks(
AchievementController _achievementController,
HttpResponseUtil _httpResponseUtil
)
@@ -16,9 +16,8 @@ public class ClientLogCallbacks(
ConfigServer _configServer,
LocalisationService _localisationService
// ModLoadOrder _modLoadOrder // TODO: needs implementing
)
)
{
/// <summary>
/// Handle /singleplayer/log
/// </summary>
@@ -39,11 +38,11 @@ public class ClientLogCallbacks(
public string ReleaseNotes()
{
var data = _configServer.GetConfig<CoreConfig>().Release;
data.BetaDisclaimerText = ProgramStatics.MODS()
? _localisationService.GetText("release-beta-disclaimer-mods-enabled")
? _localisationService.GetText("release-beta-disclaimer-mods-enabled")
: _localisationService.GetText("release-beta-disclaimer");
data.BetaDisclaimerAcceptText = _localisationService.GetText("release-beta-disclaimer-accept");
data.ServerModsLoadedText = _localisationService.GetText("release-server-mods-loaded");
data.ServerModsLoadedDebugText = _localisationService.GetText("release-server-mods-debug-message");
@@ -55,7 +54,7 @@ public class ClientLogCallbacks(
data.IsBeta = ProgramStatics.ENTRY_TYPE() == EntryType.BLEEDING_EDGE || ProgramStatics.ENTRY_TYPE() == EntryType.BLEEDING_EDGE_MODS;
data.IsModdable = ProgramStatics.MODS();
data.IsModded = false; // TODO
return _httpResponseUtil.NoBody(data);
}
+3 -8
View File
@@ -149,13 +149,9 @@ public class DataCallbacks(
{
var localeId = url.Replace("/client/menu/locale/", "");
var locales = _databaseService.GetLocales();
var result = locales.Menu?[localeId]
?? locales.Menu?.FirstOrDefault(m => m.Key == "en").Value;
var result = locales.Menu?[localeId] ?? locales.Menu?.FirstOrDefault(m => m.Key == "en").Value;
if (result == null)
{
throw new Exception($"Unable to determine locale for request with {localeId}");
}
if (result == null) throw new Exception($"Unable to determine locale for request with {localeId}");
return _httpResponseUtil.GetBody(result);
}
@@ -171,8 +167,7 @@ public class DataCallbacks(
{
var localeId = url.Replace("/client/locale/", "");
var locales = _databaseService.GetLocales();
var result = locales.Global?[localeId].Value
?? locales.Global?.FirstOrDefault(m => m.Key == "en").Value.Value;
var result = locales.Global?[localeId].Value ?? locales.Global?.FirstOrDefault(m => m.Key == "en").Value.Value;
return _httpResponseUtil.GetUnclearedBody(result);
}
@@ -41,7 +41,7 @@ public class DialogueCallbacks(
{
var chatServer = new List<ChatServer>
{
new ChatServer
new()
{
Id = _hashUtil.Generate(),
RegistrationId = 20,
@@ -51,7 +51,7 @@ public class DialogueCallbacks(
VersionId = "bgkidft87ddd",
Ip = "",
Port = 0,
Chats = [ new Chat { Id = "0", Members = 0 } ],
Chats = [new Chat { Id = "0", Members = 0 }]
}
};
+1 -2
View File
@@ -13,9 +13,8 @@ public class HealthCallbacks(
HttpResponseUtil _httpResponseUtil,
ProfileHelper _profileHelper,
HealthController _healthController
)
)
{
/// <summary>
/// Custom spt server request found in modules/QTEPatch.cs
/// </summary>
+21 -19
View File
@@ -14,7 +14,7 @@ namespace Core.Callbacks;
public class HideoutCallbacks(
HideoutController _hideoutController,
ConfigServer _configServer
) : OnUpdate
) : OnUpdate
{
private readonly HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
@@ -23,7 +23,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
{
_hideoutController.StartUpgrade(pmcData, request, sessionID, output);
_hideoutController.StartUpgrade(pmcData, request, sessionID, output);
return output;
}
@@ -33,7 +33,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionID, ItemEventRouterResponse output)
{
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
return output;
}
@@ -43,7 +43,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionID)
{
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
}
/// <summary>
@@ -51,7 +51,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID)
{
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
}
/// <summary>
@@ -59,7 +59,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID)
{
return _hideoutController.ToggleArea(pmcData, request, sessionID);
return _hideoutController.ToggleArea(pmcData, request, sessionID);
}
/// <summary>
@@ -67,7 +67,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionID)
{
return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
}
/// <summary>
@@ -75,7 +75,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionID)
{
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
}
/// <summary>
@@ -83,7 +83,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
{
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
}
/// <summary>
@@ -91,7 +91,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID)
{
return _hideoutController.TakeProduction(pmcData, request, sessionID);
return _hideoutController.TakeProduction(pmcData, request, sessionID);
}
/// <summary>
@@ -99,7 +99,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData request, string sessionID, ItemEventRouterResponse output)
{
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
return output;
}
@@ -107,9 +107,10 @@ public class HideoutCallbacks(
/// <summary>
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
/// </summary>
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID, ItemEventRouterResponse output)
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID,
ItemEventRouterResponse output)
{
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
return output;
}
@@ -119,7 +120,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
{
return _hideoutController.ImproveArea(sessionID, pmcData, request);
return _hideoutController.ImproveArea(sessionID, pmcData, request);
}
/// <summary>
@@ -127,7 +128,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutCancelProductionRequestData request, string sessionID)
{
return _hideoutController.CancelProduction(sessionID, pmcData, request);
return _hideoutController.CancelProduction(sessionID, pmcData, request);
}
/// <summary>
@@ -135,7 +136,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request, string sessionID)
{
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
}
/// <summary>
@@ -143,7 +144,7 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData request, string sessionID)
{
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
}
/// <summary>
@@ -151,13 +152,14 @@ public class HideoutCallbacks(
/// </summary>
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData request, string sessionID)
{
return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, request);
return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, request);
}
/**
* Handle client/game/profile/items/moving - hideoutCustomizationSetMannequinPose
*/
public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(PmcData pmcData, HideoutCustomizationSetMannequinPoseRequest request, string sessionId) {
public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(PmcData pmcData, HideoutCustomizationSetMannequinPoseRequest request, string sessionId)
{
return _hideoutController.HideoutCustomizationSetMannequinPose(sessionId, pmcData, request);
}
+2 -2
View File
@@ -10,9 +10,9 @@ public class HttpCallbacks(HttpServer _httpServer, ApplicationContext _applicati
{
public Task OnLoad()
{
_httpServer.Load( _applicationContext.GetLatestValue(ContextVariableType.APP_BUILDER)?.GetValue<WebApplicationBuilder>());
_httpServer.Load(_applicationContext.GetLatestValue(ContextVariableType.APP_BUILDER)?.GetValue<WebApplicationBuilder>());
_applicationContext.ClearValues(ContextVariableType.APP_BUILDER);
return Task.CompletedTask;
}
+1 -1
View File
@@ -10,7 +10,7 @@ namespace Core.Callbacks;
public class InraidCallbacks(
InRaidController _inRaidController,
HttpResponseUtil _httpResponseUtil
)
)
{
/// <summary>
/// Handle client/location/getLocalloot
@@ -18,7 +18,7 @@ public class InsuranceCallbacks(
InsuranceService _insuranceService,
HttpResponseUtil _httpResponseUtil,
ConfigServer _configServer
)
)
: OnUpdate
{
private InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>();
@@ -32,7 +32,7 @@ public class InsuranceCallbacks(
/// <returns></returns>
public string GetInsuranceCost(string url, GetInsuranceCostRequestData info, string sessionID)
{
return _httpResponseUtil.GetBody(_insuranceController.Cost(info, sessionID));
return _httpResponseUtil.GetBody(_insuranceController.Cost(info, sessionID));
}
/// <summary>
@@ -50,11 +50,9 @@ public class InsuranceCallbacks(
public bool OnUpdate(long timeSinceLastRun)
{
if (timeSinceLastRun > Math.Max(_insuranceConfig.RunIntervalSeconds, 1))
{
// _insuranceController.ProcessReturn();
// TODO: InsuranceController is not implemented rn
return true;
}
return false;
}
+15 -15
View File
@@ -11,12 +11,12 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
{
public string HandleEvents(string url, ItemEventRouterRequest info, string sessionID)
{
var eventResponse = _itemEventRouter.HandleEvents(info, sessionID);
var result = IsCriticalError(eventResponse.Warnings)
? _httpResponseUtil.GetBody(eventResponse, GetErrorCode(eventResponse.Warnings), eventResponse.Warnings[0].ErrorMessage)
: _httpResponseUtil.GetBody(eventResponse);
var eventResponse = _itemEventRouter.HandleEvents(info, sessionID);
var result = IsCriticalError(eventResponse.Warnings)
? _httpResponseUtil.GetBody(eventResponse, GetErrorCode(eventResponse.Warnings), eventResponse.Warnings[0].ErrorMessage)
: _httpResponseUtil.GetBody(eventResponse);
return result;
return result;
}
/// <summary>
@@ -26,11 +26,8 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
/// <returns></returns>
public bool IsCriticalError(List<Warning>? warnings)
{
if (warnings is null)
{
return false;
}
if (warnings is null) return false;
// List of non-critical error codes, we return true if any error NOT included is passed in
var nonCriticalErrorCodes = new List<BackendErrorCodes> { BackendErrorCodes.NotEnoughSpace };
@@ -38,19 +35,22 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
{
if (!Enum.TryParse(warning.Code, out BackendErrorCodes code))
throw new Exception($"Unable to parse [{warning.Code}] to BackendErrorCode.");
if (!nonCriticalErrorCodes.Contains(code))
return true;
}
return false;
}
public int GetErrorCode(List<Warning> warnings)
{
// Cast int to string to get the error code of 220 for Unknown Error.
return int.Parse((warnings[0].Code is null || warnings[0].Code == "None"
? ((int) BackendErrorCodes.UnknownError).ToString()
: warnings.FirstOrDefault()?.Code) ?? string.Empty);
return int.Parse(
(warnings[0].Code is null || warnings[0].Code == "None"
? ((int)BackendErrorCodes.UnknownError).ToString()
: warnings.FirstOrDefault()?.Code) ??
string.Empty
);
}
}
+23 -13
View File
@@ -15,7 +15,8 @@ public class LauncherV2Callbacks(
{
public string Ping()
{
return _httpResponseUtil.NoBody(new LauncherV2PingResponse
return _httpResponseUtil.NoBody(
new LauncherV2PingResponse
{
Response = _launcherV2Controller.Ping()
}
@@ -24,15 +25,18 @@ public class LauncherV2Callbacks(
public string Types()
{
return _httpResponseUtil.NoBody(new LauncherV2TypesResponse
{
Response = _launcherV2Controller.Types()
});
return _httpResponseUtil.NoBody(
new LauncherV2TypesResponse
{
Response = _launcherV2Controller.Types()
}
);
}
public string Login(LoginRequestData info)
{
return _httpResponseUtil.NoBody(new LauncherV2LoginResponse
return _httpResponseUtil.NoBody(
new LauncherV2LoginResponse
{
Response = _launcherV2Controller.Login(info)
}
@@ -41,7 +45,8 @@ public class LauncherV2Callbacks(
public string Register(RegisterData info)
{
return _httpResponseUtil.NoBody(new LauncherV2RegisterResponse
return _httpResponseUtil.NoBody(
new LauncherV2RegisterResponse
{
Response = _launcherV2Controller.Register(info),
Profiles = _profileController.GetMiniProfiles()
@@ -51,17 +56,19 @@ public class LauncherV2Callbacks(
public string PasswordChange(ChangeRequestData info)
{
return _httpResponseUtil.NoBody(new LauncherV2PasswordChangeResponse
return _httpResponseUtil.NoBody(
new LauncherV2PasswordChangeResponse
{
Response = _launcherV2Controller.PasswordChange(info),
Profiles = _profileController.GetMiniProfiles()
}
);
}
public string Remove(LoginRequestData info)
{
return _httpResponseUtil.NoBody(new LauncherV2RemoveResponse
return _httpResponseUtil.NoBody(
new LauncherV2RemoveResponse
{
Response = _launcherV2Controller.Remove(info),
Profiles = _profileController.GetMiniProfiles()
@@ -71,7 +78,8 @@ public class LauncherV2Callbacks(
public string CompatibleVersion()
{
return _httpResponseUtil.NoBody(new LauncherV2VersionResponse
return _httpResponseUtil.NoBody(
new LauncherV2VersionResponse
{
Response = new LauncherV2CompatibleVersion
{
@@ -84,7 +92,8 @@ public class LauncherV2Callbacks(
public string Mods()
{
return _httpResponseUtil.NoBody(new LauncherV2ModsResponse
return _httpResponseUtil.NoBody(
new LauncherV2ModsResponse
{
Response = _launcherV2Controller.LoadedMods()
}
@@ -93,7 +102,8 @@ public class LauncherV2Callbacks(
public string Profiles()
{
return _httpResponseUtil.NoBody(new LauncherV2ProfilesResponse
return _httpResponseUtil.NoBody(
new LauncherV2ProfilesResponse
{
Response = _profileController.GetMiniProfiles()
}
+1 -1
View File
@@ -108,7 +108,7 @@ public class MatchCallbacks(
/// <returns></returns>
public string AcceptGroupInvite(string url, RequestIdRequest info, string sessionID)
{
return _httpResponseUtil.GetBody(new List<GroupCharacter>() { new GroupCharacter() });
return _httpResponseUtil.GetBody(new List<GroupCharacter>() { new() });
}
/// <summary>
@@ -9,7 +9,6 @@ namespace Core.Callbacks;
[Injectable]
public class NoteCallbacks(NoteController _noteController)
{
/// <summary>
/// Handle AddNote event
/// </summary>
+1 -4
View File
@@ -94,10 +94,7 @@ public class ProfileCallbacks(
public string GetReservedNickname(string url, EmptyRequestData info, string sessionID)
{
var fullProfile = _profileHelper.GetFullProfile(sessionID);
if (fullProfile?.ProfileInfo?.Username is not null)
{
return _httpResponse.GetBody(fullProfile?.ProfileInfo?.Username);
}
if (fullProfile?.ProfileInfo?.Username is not null) return _httpResponse.GetBody(fullProfile?.ProfileInfo?.Username);
return _httpResponse.GetBody("SPTarkov");
}
+14 -12
View File
@@ -21,7 +21,7 @@ public class RagfairCallbacks(
RagfairTaxService _ragfairTaxService,
RagfairPriceService _ragfairPriceService,
ConfigServer _configServer
) : OnLoad, OnUpdate
) : OnLoad, OnUpdate
{
private RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
@@ -39,19 +39,21 @@ public class RagfairCallbacks(
public bool OnUpdate(long timeSinceLastRun)
{
if (timeSinceLastRun > _ragfairConfig.RunIntervalSeconds) {
if (timeSinceLastRun > _ragfairConfig.RunIntervalSeconds)
{
// There is a flag inside this class that only makes it run once.
_ragfairServer.AddPlayerOffers();
// Check player offers and mail payment to player if sold
_ragfairController.Update();
// Process all offers / expire offers
_ragfairServer.Update();
return true;
}
return false;
// Check player offers and mail payment to player if sold
_ragfairController.Update();
// Process all offers / expire offers
_ragfairServer.Update();
return true;
}
return false;
}
/// <summary>
+1 -1
View File
@@ -12,7 +12,7 @@ public class SaveCallbacks(
SaveServer _saveServer,
ConfigServer _configServer,
BackupService _backupService
)
)
: OnLoad, OnUpdate
{
private readonly CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
+1 -1
View File
@@ -18,7 +18,7 @@ public class TraderCallbacks(
) : OnLoad, OnUpdate
{
private readonly TraderConfig _traderConfig = _configServer.GetConfig<TraderConfig>();
public Task OnLoad()
{
_traderController.Load();
+1 -4
View File
@@ -25,10 +25,7 @@ public class ApplicationContext
lock (variablesLock)
{
var values = new List<ContextVariable>();
if (variables.TryGetValue(type, out var savedValues))
{
values.AddRange(savedValues);
}
if (variables.TryGetValue(type, out var savedValues)) values.AddRange(savedValues);
return values;
}
+2 -1
View File
@@ -4,7 +4,8 @@ public class ContextVariable(object value, ContextVariableType contextVariableIn
{
private readonly DateTime _timestamp = DateTime.UtcNow;
public T GetValue<T>() {
public T GetValue<T>()
{
return (T)value;
}
@@ -23,11 +23,10 @@ public class AchievementController(
var stats = new Dictionary<string, int>();
foreach (var achievement in achievements)
{
if (achievement.Id != null) stats.Add(achievement.Id, 0);
}
if (achievement.Id != null)
stats.Add(achievement.Id, 0);
return new()
return new CompletedAchievementsResponse
{
Elements = stats
};
+25 -69
View File
@@ -52,10 +52,7 @@ public class BotController(
.First(p => p.Name.ToLower() == (typeInLower == "assaultgroup" ? "assault" : typeInLower))
.GetValue(_botConfig.PresetBatch);
if (value != null)
{
return value;
}
if (value != null) return value;
_logger.Warning(_localisationService.GetText("bot-bot_preset_count_value_missing", type));
return 30;
@@ -70,18 +67,12 @@ public class BotController(
{
var difficulty = diffLevel.ToLower();
if (!(raidConfig != null || ignoreRaidSettings))
{
_logger.Error(_localisationService.GetText("bot-missing_application_context", "RAID_CONFIGURATION"));
}
if (!(raidConfig != null || ignoreRaidSettings)) _logger.Error(_localisationService.GetText("bot-missing_application_context", "RAID_CONFIGURATION"));
// 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";
if (botDifficultyDropDownValue != "asonline")
{
difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
}
if (botDifficultyDropDownValue != "asonline") difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
var botDb = _databaseService.GetBots();
return _botDifficultyHelper.GetBotDifficultySettings(type, difficulty, botDb);
@@ -96,10 +87,7 @@ public class BotController(
var botTypes = Enum.GetValues<WildSpawnType>().Select(item => item.ToString()).ToList();
foreach (var botType in botTypes)
{
if (botTypesDb is null)
{
continue;
}
if (botTypesDb is null) continue;
// If bot is usec/bear, swap to different name
var botTypeLower = _botHelper.IsBotPmc(botType)
@@ -113,10 +101,7 @@ public class BotController(
{
// No bot of this type found, copy details from assault
result[botTypeLower] = result["assault"];
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to find bot: {botTypeLower} in db, copying 'assault'");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to find bot: {botTypeLower} in db, copying 'assault'");
continue;
}
@@ -131,10 +116,7 @@ public class BotController(
foreach (var (difficultyName, _) in botDetails.BotDifficulty)
{
// Bot doesn't exist in result, add
if (!result.ContainsKey(botNameKey))
{
result.TryAdd(botNameKey, new Dictionary<string, DifficultyCategories>());
}
if (!result.ContainsKey(botNameKey)) result.TryAdd(botNameKey, new Dictionary<string, DifficultyCategories>());
// Store all difficulty values in dict keyed by difficulty type e.g. easy/normal/impossible
result[botNameKey].Add(difficultyName, GetBotDifficulty(botNameKey, difficultyName, null, true));
@@ -147,7 +129,7 @@ public class BotController(
public List<BotBase> Generate(string sessionId, GenerateBotsRequestData info)
{
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
// Use this opportunity to create and cache bots for later retrieval
var multipleBotTypesRequested = info.Conditions?.Count > 1;
return multipleBotTypesRequested
@@ -166,7 +148,6 @@ public class BotController(
var tasks = new List<Task>();
// Map conditions to promises for bot generation
foreach (var condition in request.Conditions ?? [])
{
tasks.Add(
Task.Factory.StartNew(
() =>
@@ -185,14 +166,10 @@ public class BotController(
}
)
);
}
Task.WaitAll(tasks.ToArray());
stopwatch.Stop();
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache");
return [];
}
@@ -220,24 +197,17 @@ public class BotController(
if (botCacheCount >= botGenerationDetails.BotCountToGenerate)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Cache already has sufficient {cacheKey} bots: {botCacheCount}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Cache already has sufficient {cacheKey} bots: {botCacheCount}");
return;
}
// We're below desired count, add bots to cache
var botsToGenerate = botGenerationDetails.BotCountToGenerate - botCacheCount;
var progressWriter = new ProgressWriter(botGenerationDetails.BotCountToGenerate.GetValueOrDefault(30));
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Generating {botsToGenerate} bots for cacheKey: {cacheKey}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generating {botsToGenerate} bots for cacheKey: {cacheKey}");
for (var i = 0; i < botsToGenerate; i++)
{
try
{
var detailsClone = _cloner.Clone(botGenerationDetails);
@@ -248,15 +218,12 @@ public class BotController(
{
_logger.Error($"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message}");
}
}
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Generated {botGenerationDetails.BotCountToGenerate} {botGenerationDetails.Role}" +
$"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) {botGenerationDetails.BotDifficulty}bots"
);
}
}
private List<BotBase> ReturnSingleBotFromCache(string sessionId, GenerateBotsRequestData request)
@@ -271,7 +238,7 @@ public class BotController(
{
Role = requestedBot?.Role,
Limit = 5,
Difficulty = requestedBot?.Difficulty,
Difficulty = requestedBot?.Difficulty
};
var botGenerationDetails = GetBotGenerationDetailsForWave(
condition,
@@ -321,13 +288,9 @@ public class BotController(
{
var bossConvertPercent = bossConvertMinMax.GetByJsonProp<MinMax>(requestedBot?.Role?.ToLower() ?? string.Empty);
if (bossConvertPercent is not null)
{
// Roll a percentage check if we should convert scav to boss
if (_randomUtil.GetChance100(_randomUtil.GetDouble(bossConvertPercent.Min!.Value, bossConvertPercent.Max!.Value)))
{
UpdateBotGenerationDetailsToRandomBoss(botGenerationDetails, bossesToConvertToWeights);
}
}
}
// Create a compound key to store bots in cache against
@@ -341,14 +304,12 @@ public class BotController(
{
// No bot in cache, generate new and store in cache
GenerateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey);
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Generated {botGenerationDetails.BotCountToGenerate} " +
$"{botGenerationDetails.Role} ({botGenerationDetails.EventRole ?? ""}) {botGenerationDetails.BotDifficulty} bots"
);
}
}
var desiredBot = _botGenerationCacheService.GetBot(cacheKey);
@@ -400,10 +361,7 @@ public class BotController(
.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
?.GetValue<GetRaidConfigurationRequestData>();
if (raidSettings is null)
{
_logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext"));
}
if (raidSettings is null) _logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext"));
return raidSettings;
}
@@ -432,9 +390,9 @@ public class BotController(
BotRelativeLevelDeltaMin = _pmcConfig.BotRelativeLevelDeltaMin,
BotCountToGenerate = botCountToGenerate,
BotDifficulty = condition.Difficulty,
LocationSpecificPmcLevelOverride = this.GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within
LocationSpecificPmcLevelOverride = GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within
IsPlayerScav = false,
AllPmcsHaveSameNameAsPlayer = allPmcsHaveSameNameAsPlayer,
AllPmcsHaveSameNameAsPlayer = allPmcsHaveSameNameAsPlayer
};
}
@@ -442,11 +400,9 @@ public class BotController(
{
var botCap = _botConfig.MaxBotCap.FirstOrDefault(x => x.Key.ToLower() == location.ToLower());
if (location == "default")
{
_logger.Warning(
_localisationService.GetText("bot-no_bot_cap_found_for_location", location.ToLower())
);
}
return botCap.Value;
}
@@ -457,7 +413,7 @@ public class BotController(
{
PmcType = _pmcConfig.PmcType,
Assault = _botConfig.AssaultBrainType,
PlayerScav = _botConfig.PlayerScavBrainType,
PlayerScav = _botConfig.PlayerScavBrainType
};
}
}
@@ -465,11 +421,11 @@ public class BotController(
public record AiBotBrainTypes
{
[JsonPropertyName("pmc")]
public Dictionary<string,Dictionary<string,Dictionary<string,double>>> PmcType { get; set; }
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType { get; set; }
[JsonPropertyName("assault")]
public Dictionary<string,Dictionary<string,int>> Assault { get; set; }
public Dictionary<string, Dictionary<string, int>> Assault { get; set; }
[JsonPropertyName("playerScav")]
public Dictionary<string,Dictionary<string,int>> PlayerScav { get; set; }
public Dictionary<string, Dictionary<string, int>> PlayerScav { get; set; }
}
+6 -13
View File
@@ -36,9 +36,7 @@ public class BuildController(
const string secureContainerSlotId = "SecuredContainer";
var profile = _profileHelper.GetFullProfile(sessionID);
if (profile is not null && profile.UserBuildData is null)
{
profile.UserBuildData = new UserBuilds { EquipmentBuilds = [], WeaponBuilds = [], MagazineBuilds = [] };
}
// Ensure the secure container in the default presets match what the player has equipped
var defaultEquipmentPresetsClone = _cloner.Clone(
@@ -52,18 +50,13 @@ public class BuildController(
x => x.SlotId == secureContainerSlotId
);
if (playerSecureContainer is not null && playerSecureContainer.Template != firstDefaultItemsSecureContainer?.Template)
{
// Default equipment presets' secure container tpl doesn't match players secure container tpl
foreach (var defaultPreset in defaultEquipmentPresetsClone ?? [])
{
// Find presets secure container
var secureContainer = defaultPreset.Items?.FirstOrDefault(item => item.SlotId == secureContainerSlotId);
if (secureContainer is not null)
{
secureContainer.Template = playerSecureContainer.Template;
}
if (secureContainer is not null) secureContainer.Template = playerSecureContainer.Template;
}
}
// Clone player build data from profile and append the above defaults onto end
var userBuildsClone = _cloner.Clone(profile?.UserBuildData);
@@ -87,7 +80,7 @@ public class BuildController(
body.Root = body.Items.FirstOrDefault().Id;
// Create new object ready to save into profile userbuilds.weaponBuilds
WeaponBuild newBuild = new WeaponBuild { Id = body.Id, Name = body.Name, Root = body.Root, Items = body.Items };
var newBuild = new WeaponBuild { Id = body.Id, Name = body.Name, Root = body.Root, Items = body.Items };
var profile = _profileHelper.GetFullProfile(sessionId);
@@ -124,13 +117,13 @@ public class BuildController(
// Root ID and the base item ID need to match.
request.Items = _itemHelper.ReplaceIDs(request.Items, pmcData);
EquipmentBuild newBuild = new EquipmentBuild
var newBuild = new EquipmentBuild
{
Id = request.Id,
Name = request.Name,
BuildType = EquipmentBuildType.Custom,
Root = request.Items[0].Id,
Items = request.Items,
Items = request.Items
};
var existingBuild = existingSavedEquipmentBuilds.FirstOrDefault(
@@ -167,14 +160,14 @@ public class BuildController(
/// <param name="request"></param>
public void CreateMagazineTemplate(string sessionId, SetMagazineRequest request)
{
MagazineBuild result = new MagazineBuild
var result = new MagazineBuild
{
Id = request.Id,
Name = request.Name,
Caliber = request.Caliber,
TopCount = request.TopCount,
BottomCount = request.BottomCount,
Items = request.Items,
Items = request.Items
};
var profile = _profileHelper.GetFullProfile(sessionId);
@@ -8,9 +8,8 @@ namespace Core.Controllers;
[Injectable]
public class ClientLogController(
ISptLogger<ClientLogController> _logger
)
)
{
/// <summary>
/// Handle /singleplayer/log
/// </summary>
@@ -55,10 +55,7 @@ public class CustomizationController(
)
.ToList();
if (matchingSuits == null)
{
throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
}
if (matchingSuits == null) throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
return matchingSuits;
}
@@ -104,7 +101,7 @@ public class CustomizationController(
return output;
}
// Charge player for buying item
PayForClothingItems(sessionId, pmcData, buyClothingRequest.Items, output);
@@ -127,20 +124,14 @@ public class CustomizationController(
{
var suits = _saveServer.GetProfile(sessionId).Suits;
if (suits is null || suits.Count == 0)
{
return false;
}
if (suits is null || suits.Count == 0) return false;
return suits.Contains(suitId);
}
private Suit? GetTraderClothingOffer(string sessionId, string? offerId)
{
var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId);
if (foundSuit is null)
{
_logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId));
}
if (foundSuit is null) _logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId));
return foundSuit;
}
@@ -156,11 +147,8 @@ public class CustomizationController(
List<PaymentItemForClothing>? itemsToPayForClothingWith,
ItemEventRouterResponse output)
{
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0)
{
return;
}
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0) return;
foreach (var inventoryItemToProcess in itemsToPayForClothingWith)
{
var options = new ProcessBuyTradeRequestData
@@ -189,9 +177,7 @@ public class CustomizationController(
foreach (var trader in traders)
if (trader.Value.Base?.CustomizationSeller is not null && trader.Value.Base.CustomizationSeller.Value)
{
result.AddRange(GetTraderSuits(trader.Key, sessionId));
}
return result;
}
@@ -220,10 +206,7 @@ public class CustomizationController(
var customisationResultsClone = _cloner.Clone(_databaseService.GetTemplates().CustomisationStorage);
var profile = _profileHelper.GetFullProfile(sessionId);
if (profile is null)
{
return customisationResultsClone!;
}
if (profile is null) return customisationResultsClone!;
customisationResultsClone!.AddRange(profile.CustomisationUnlocks ?? []);
@@ -240,7 +223,6 @@ public class CustomizationController(
public ItemEventRouterResponse SetCustomisation(string sessionId, CustomizationSetRequest request, PmcData pmcData)
{
foreach (var customisation in request.Customizations)
{
switch (customisation.Type)
{
case "dogTag":
@@ -253,7 +235,6 @@ public class CustomizationController(
_logger.Error($"Unhandled customisation type: {customisation.Type}");
break;
}
}
return _eventOutputHolder.GetOutput(sessionId);
}
@@ -285,9 +266,6 @@ public class CustomizationController(
}
// Feet
if (dbSuit.Parent == _lowerParentClothingId)
{
pmcData.Customization.Feet = dbSuit.Properties.Feet;
}
if (dbSuit.Parent == _lowerParentClothingId) pmcData.Customization.Feet = dbSuit.Properties.Feet;
}
}
@@ -37,9 +37,7 @@ public class DialogueController(
public void RegisterChatBot(IDialogueChatBot chatBot) // TODO: this is in with the helper types
{
if (_dialogueChatBots.Any(cb => cb.GetChatBot().Id == chatBot.GetChatBot().Id))
{
_logger.Error(_localisationService.GetText("dialog-chatbot_id_already_exists", chatBot.GetChatBot().Id));
}
_dialogueChatBots.Add(chatBot);
}
@@ -51,10 +49,7 @@ public class DialogueController(
public void Update()
{
var profiles = _saveServer.GetProfiles();
foreach (var kvp in profiles)
{
RemoveExpiredItemsFromMessages(kvp.Key);
}
foreach (var kvp in profiles) RemoveExpiredItemsFromMessages(kvp.Key);
}
/// <summary>
@@ -70,23 +65,19 @@ public class DialogueController(
// Add any friends the user has after the chatbots
var profile = _profileHelper.GetFullProfile(sessionId);
if (profile?.FriendProfileIds is not null)
{
foreach (var friendId in profile.FriendProfileIds)
{
var friendProfile = _profileHelper.GetChatRoomMemberFromSessionId(friendId);
if (friendProfile is not null)
{
friends.Add(
new UserDialogInfo
{
Id = friendProfile.Id,
Aid = friendProfile.Aid,
Info = friendProfile.Info,
Info = friendProfile.Info
}
);
}
}
}
return new GetFriendListDataResponse
{
@@ -105,10 +96,7 @@ public class DialogueController(
foreach (var bot in _dialogueChatBots)
{
var botData = bot.GetChatBot();
if (chatBotConfig.EnabledBots.ContainsKey(botData.Id!))
{
activeBots.Add(botData);
}
if (chatBotConfig.EnabledBots.ContainsKey(botData.Id!)) activeBots.Add(botData);
}
return activeBots;
@@ -124,10 +112,7 @@ public class DialogueController(
public List<DialogueInfo> GenerateDialogueList(string sessionId)
{
var data = new List<DialogueInfo>();
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId))
{
data.Add(GetDialogueInfo(dialogueId.Key, sessionId));
}
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) data.Add(GetDialogueInfo(dialogueId.Key, sessionId));
return data;
}
@@ -153,7 +138,7 @@ public class DialogueController(
New = dialogue?.New,
AttachmentsNew = dialogue?.AttachmentsNew,
Pinned = dialogue?.Pinned,
Users = GetDialogueUsers(dialogue, dialogue?.Type, sessionId),
Users = GetDialogueUsers(dialogue, dialogue?.Type, sessionId)
};
return result;
@@ -177,7 +162,6 @@ public class DialogueController(
if (messageType == MessageType.USER_MESSAGE &&
dialog?.Users is not null &&
dialog.Users.All(userDialog => userDialog.Id != profile.CharacterData?.PmcData?.SessionId))
{
dialog.Users.Add(
new UserDialogInfo
{
@@ -189,11 +173,10 @@ public class DialogueController(
Nickname = profile.CharacterData?.PmcData?.Info?.Nickname,
Side = profile.CharacterData?.PmcData?.Info?.Side,
MemberCategory = profile.CharacterData?.PmcData?.Info?.MemberCategory,
SelectedMemberCategory = profile.CharacterData?.PmcData?.Info?.SelectedMemberCategory,
},
SelectedMemberCategory = profile.CharacterData?.PmcData?.Info?.SelectedMemberCategory
}
}
);
}
return dialog?.Users!;
}
@@ -225,7 +208,7 @@ public class DialogueController(
{
Messages = dialogue.Messages,
Profiles = GetProfilesForMail(fullProfile, dialogue.Users),
HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages!),
HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages!)
};
}
@@ -249,22 +232,16 @@ public class DialogueController(
Pinned = false,
Messages = [],
New = 0,
Type = request.Type,
Type = request.Type
};
if (request.Type != MessageType.USER_MESSAGE)
{
return profile.DialogueRecords[request.DialogId!];
}
if (request.Type != MessageType.USER_MESSAGE) return profile.DialogueRecords[request.DialogId!];
var dialogue = profile.DialogueRecords[request.DialogId!];
dialogue.Users = [];
var chatBot = _dialogueChatBots.FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId);
if (chatBot is null)
{
return profile.DialogueRecords[request.DialogId!];
}
if (chatBot is null) return profile.DialogueRecords[request.DialogId!];
dialogue.Users ??= [];
dialogue.Users.Add(chatBot.GetChatBot());
@@ -282,17 +259,12 @@ public class DialogueController(
{
List<UserDialogInfo> result = [];
if (userDialogs is null)
{
// Nothing to add
return result;
}
result.AddRange(userDialogs);
if (result.Any(userDialog => userDialog.Id == fullProfile.ProfileInfo?.ProfileId))
{
return result;
}
if (result.Any(userDialog => userDialog.Id == fullProfile.ProfileInfo?.ProfileId)) return result;
// Player doesn't exist, add them in before returning
var pmcProfile = fullProfile.CharacterData?.PmcData;
@@ -307,7 +279,7 @@ public class DialogueController(
Side = pmcProfile?.Info?.Side,
Level = pmcProfile?.Info?.Level,
MemberCategory = pmcProfile?.Info?.MemberCategory,
SelectedMemberCategory = pmcProfile?.Info?.SelectedMemberCategory,
SelectedMemberCategory = pmcProfile?.Info?.SelectedMemberCategory
}
}
);
@@ -328,12 +300,8 @@ public class DialogueController(
var newAttachmentCount = 0;
var activeMessages = GetActiveMessagesFromDialog(sessionId, dialogueId);
foreach (var message in activeMessages)
{
if (message.HasRewards.GetValueOrDefault(false) && !message.RewardCollected.GetValueOrDefault(false))
{
newAttachmentCount++;
}
}
return newAttachmentCount;
}
@@ -382,7 +350,7 @@ public class DialogueController(
new
{
sessionId,
dialogueId,
dialogueId
}
)
);
@@ -410,7 +378,7 @@ public class DialogueController(
new
{
sessionId,
dialogueId,
dialogueId
}
)
);
@@ -437,7 +405,7 @@ public class DialogueController(
"dialogue-unable_to_find_dialogs_in_profile",
new
{
sessionId,
sessionId
}
)
);
@@ -498,11 +466,13 @@ public class DialogueController(
{
_mailSendService.SendPlayerMessageToNpc(sessionId, request.DialogId!, request.Text!);
return (_dialogueChatBots.FirstOrDefault(cb =>
cb.GetChatBot().Id == request.DialogId)
?.HandleMessage(sessionId, request)
?? request.DialogId)
?? string.Empty;
return (_dialogueChatBots.FirstOrDefault(
cb =>
cb.GetChatBot().Id == request.DialogId
)
?.HandleMessage(sessionId, request) ??
request.DialogId) ??
string.Empty;
}
/// <summary>
@@ -521,10 +491,7 @@ public class DialogueController(
/// <param name="sessionId">Session id</param>
private void RemoveExpiredItemsFromMessages(string sessionId)
{
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId))
{
RemoveExpiredItemsFromMessage(sessionId, dialogueId.Key);
}
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) RemoveExpiredItemsFromMessage(sessionId, dialogueId.Key);
}
/// <summary>
@@ -535,18 +502,11 @@ public class DialogueController(
private void RemoveExpiredItemsFromMessage(string sessionId, string dialogueId)
{
var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId);
if (!dialogs.TryGetValue(dialogueId, out var dialog))
{
return;
}
if (!dialogs.TryGetValue(dialogueId, out var dialog)) return;
foreach (var message in dialog.Messages ?? [])
{
if (MessageHasExpired(message))
{
message.Items = new MessageItems();
}
}
}
/**
@@ -564,30 +524,25 @@ public class DialogueController(
// To avoid needing to jump between profiles, auto-accept all friend requests
var friendProfile = _profileHelper.GetFullProfile(request.To);
if (friendProfile?.CharacterData?.PmcData is null)
{
return new FriendRequestSendResponse
{
Status = BackendErrorCodes.PlayerProfileNotFound,
RequestId = "", // Unused in an error state
RetryAfter = 600,
RetryAfter = 600
};
}
// Only add the profile to the friends list if it doesn't already exist
var profile = _saveServer.GetProfile(sessionID);
if (!profile.FriendProfileIds.Contains(request.To))
{
profile.FriendProfileIds.Add(request.To);
}
if (!profile.FriendProfileIds.Contains(request.To)) profile.FriendProfileIds.Add(request.To);
// We need to delay this so that the friend request gets properly added to the clientside list before we accept it
_ = new Timer(
_ =>
{
WsFriendsListAccept notification = new WsFriendsListAccept
var notification = new WsFriendsListAccept
{
EventType = NotificationEventType.friendListRequestAccept,
Profile = _profileHelper.GetChatRoomMemberFromPmcProfile(friendProfile.CharacterData.PmcData),
Profile = _profileHelper.GetChatRoomMemberFromPmcProfile(friendProfile.CharacterData.PmcData)
};
_notificationSendHelper.SendMessage(sessionID, notification);
},
@@ -608,9 +563,6 @@ public class DialogueController(
{
var profile = _saveServer.GetProfile(sessionID);
var friendIndex = profile.FriendProfileIds.IndexOf(request.FriendId);
if (friendIndex != -1)
{
profile.FriendProfileIds.RemoveAt(friendIndex);
}
if (friendIndex != -1) profile.FriendProfileIds.RemoveAt(friendIndex);
}
}
+36 -95
View File
@@ -82,40 +82,25 @@ public class GameController(
_logger.Error($"{nameof(fullProfile)} is null on GameController.GameStart");
return;
}
fullProfile.SptData ??= new Spt { Version = "Replace_me" };
fullProfile.SptData.Migrations ??= new Dictionary<string, long>();
fullProfile.FriendProfileIds ??= [];
if (fullProfile.ProfileInfo?.IsWiped is not null && fullProfile.ProfileInfo.IsWiped.Value)
{
return;
}
if (fullProfile.ProfileInfo?.IsWiped is not null && fullProfile.ProfileInfo.IsWiped.Value) return;
fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []);
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []);
if (fullProfile.DialogueRecords is not null)
{
_profileFixerService.CheckForAndFixDialogueAttachments(fullProfile);
}
if (fullProfile.DialogueRecords is not null) _profileFixerService.CheckForAndFixDialogueAttachments(fullProfile);
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}");
var pmcProfile = fullProfile.CharacterData.PmcData;
if (_coreConfig.Fixes.FixProfileBreakingInventoryItemIssues)
{
_profileFixerService.FixProfileBreakingInventoryItemIssues(pmcProfile);
}
if (_coreConfig.Fixes.FixProfileBreakingInventoryItemIssues) _profileFixerService.FixProfileBreakingInventoryItemIssues(pmcProfile);
if (pmcProfile.Health is not null)
{
UpdateProfileHealthValues(pmcProfile);
}
if (pmcProfile.Health is not null) UpdateProfileHealthValues(pmcProfile);
if (pmcProfile.Inventory is not null)
{
@@ -143,10 +128,7 @@ public class GameController(
CheckForAndRemoveUndefinedDialogues(fullProfile);
}
if (pmcProfile.Skills?.Common is not null)
{
WarnOnActiveBotReloadSkill(pmcProfile);
}
if (pmcProfile.Skills?.Common is not null) WarnOnActiveBotReloadSkill(pmcProfile);
_seasonalEventService.GivePlayerSeasonalGifts(sessionId);
}
@@ -160,8 +142,9 @@ public class GameController(
{
var profile = _profileHelper.GetPmcProfile(sessionId);
var gameTime = profile?.Stats?.Eft?.OverallCounters?.Items?
.FirstOrDefault(c => c.Key!.Contains("LifeTime")
&& c.Key.Contains("Pmc"))?.Value ?? 0D;
.FirstOrDefault(c => c.Key!.Contains("LifeTime") && c.Key.Contains("Pmc"))
?.Value ??
0D;
var config = new GameConfigResponse
{
@@ -173,7 +156,7 @@ public class GameController(
Aid = profile?.Aid,
Taxonomy = 6,
ActiveProfileId = sessionId,
Backend = new()
Backend = new Backend
{
Lobby = _httpServerHelper.GetBackendUrl(),
Trading = _httpServerHelper.GetBackendUrl(),
@@ -185,7 +168,7 @@ public class GameController(
UtcTime = _timeUtil.GetTimeStamp(),
TotalInGame = gameTime,
SessionMode = "pve",
PurchasedGames = new()
PurchasedGames = new PurchasedGames
{
IsEftPurchased = true,
IsArenaPurchased = false
@@ -205,7 +188,7 @@ public class GameController(
string sessionId,
GameModeRequestData requestData)
{
return new()
return new GameModeResponse
{
GameMode = "pve",
BackendUrl = _httpServerHelper.GetBackendUrl()
@@ -291,7 +274,7 @@ public class GameController(
/// <returns></returns>
public SurveyResponseData GetSurvey(string sessionId)
{
return this._coreConfig.Survey;
return _coreConfig.Survey;
}
/// <summary>
@@ -301,10 +284,7 @@ public class GameController(
private void WarnOnActiveBotReloadSkill(PmcData pmcProfile)
{
var botReloadSkill = _profileHelper.GetSkillFromProfile(pmcProfile, SkillTypes.BotReload);
if (botReloadSkill?.Progress > 0)
{
_logger.Warning(_localisationService.GetText("server_start_player_active_botreload_skill"));
}
if (botReloadSkill?.Progress > 0) _logger.Warning(_localisationService.GetText("server_start_player_active_botreload_skill"));
}
/// <summary>
@@ -323,40 +303,35 @@ public class GameController(
// Base values
double energyRegenPerHour = 60;
double hydrationRegenPerHour = 60;
double hpRegenPerHour = 456.6;
var hpRegenPerHour = 456.6;
// Set new values, whatever is smallest
energyRegenPerHour += pmcProfile.Bonuses!
.Where(bonus => bonus.Type == BonusType.EnergyRegeneration)
.Aggregate(0d, (sum, bonus) => sum + (bonus.Value!.Value));
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
hydrationRegenPerHour += pmcProfile.Bonuses!
.Where(bonus => bonus.Type == BonusType.HydrationRegeneration)
.Aggregate(0d, (sum, bonus) => sum + (bonus.Value!.Value));
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
hpRegenPerHour += pmcProfile.Bonuses!
.Where(bonus => bonus.Type == BonusType.HealthRegeneration)
.Aggregate(0d, (sum, bonus) => sum + (bonus.Value!.Value));
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
// Player has energy deficit
if (Math.Abs((pmcProfile.Health?.Energy?.Current - pmcProfile.Health?.Energy?.Maximum) ?? 1) <= _deviation)
if (Math.Abs(pmcProfile.Health?.Energy?.Current - pmcProfile.Health?.Energy?.Maximum ?? 1) <= _deviation)
{
// Set new value, whatever is smallest
pmcProfile.Health!.Energy!.Current += Math.Round(energyRegenPerHour * (diffSeconds!.Value / 3600));
if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum)
{
pmcProfile.Health.Energy.Current = pmcProfile.Health.Energy.Maximum;
}
if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum) pmcProfile.Health.Energy.Current = pmcProfile.Health.Energy.Maximum;
}
// Player has hydration deficit
if (Math.Abs((pmcProfile.Health?.Hydration?.Current - pmcProfile.Health?.Hydration?.Maximum) ?? 1) <= _deviation)
if (Math.Abs(pmcProfile.Health?.Hydration?.Current - pmcProfile.Health?.Hydration?.Maximum ?? 1) <= _deviation)
{
pmcProfile.Health!.Hydration!.Current += Math.Round(hydrationRegenPerHour * (diffSeconds!.Value / 3600));
if (pmcProfile.Health.Hydration.Current > pmcProfile.Health.Hydration.Maximum)
{
pmcProfile.Health.Hydration.Current = pmcProfile.Health.Hydration.Maximum;
}
}
// Check all body parts
@@ -364,21 +339,12 @@ public class GameController(
.Select(bodyPartKvP => bodyPartKvP.Value))
{
// Check part hp
if (bodyPart.Health!.Current < bodyPart.Health.Maximum)
{
bodyPart.Health.Current += Math.Round(hpRegenPerHour * (diffSeconds!.Value / 3600));
}
if (bodyPart.Health!.Current < bodyPart.Health.Maximum) bodyPart.Health.Current += Math.Round(hpRegenPerHour * (diffSeconds!.Value / 3600));
if (bodyPart.Health.Current > bodyPart.Health.Maximum)
{
bodyPart.Health.Current = bodyPart.Health.Maximum;
}
if (bodyPart.Health.Current > bodyPart.Health.Maximum) bodyPart.Health.Current = bodyPart.Health.Maximum;
if (bodyPart.Effects is null || bodyPart.Effects.Count == 0)
{
continue;
}
if (bodyPart.Effects is null || bodyPart.Effects.Count == 0) continue;
// Look for effects
foreach (var effectKvP in bodyPart.Effects)
@@ -387,10 +353,7 @@ public class GameController(
if (effectKvP.Value.Time < 1)
{
// More than 30 mins has passed
if (diffSeconds > 1800)
{
bodyPart.Effects.Remove(effectKvP.Key);
}
if (diffSeconds > 1800) bodyPart.Effects.Remove(effectKvP.Key);
continue;
}
@@ -398,10 +361,8 @@ public class GameController(
// Decrement effect time value by difference between current time and time health was last updated
effectKvP.Value.Time -= diffSeconds;
if (effectKvP.Value.Time < 1)
{
// Effect time was sub 1, set floor it can be
effectKvP.Value.Time = 1;
}
}
}
@@ -421,16 +382,10 @@ public class GameController(
var currentTimeStamp = _timeUtil.GetTimeStamp();
// One day post-profile creation
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds)
{
_giftService.SendPraporStartingGift(pmcProfile.SessionId!, 1);
}
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds) _giftService.SendPraporStartingGift(pmcProfile.SessionId!, 1);
// Two day post-profile creation
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2)
{
_giftService.SendPraporStartingGift(pmcProfile.SessionId!, 2);
}
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2) _giftService.SendPraporStartingGift(pmcProfile.SessionId!, 2);
}
/**
@@ -438,7 +393,7 @@ public class GameController(
* @param pmcProfile Player profile
*/
protected void SendMechanicGiftsToNewProfile(PmcData pmcProfile)
{
{
_giftService.SendGiftWithSilentReceivedCheck("MechanicGiftDay1", pmcProfile.SessionId, 1);
}
@@ -495,27 +450,16 @@ public class GameController(
var bots = _databaseService.GetBots().Types;
// Official names can only be 15 chars in length
if (playerName.Length > _botConfig.BotNameLengthLimit)
{
return;
}
if (playerName.Length > _botConfig.BotNameLengthLimit) return;
// Skip if player name exists already
if (bots!.TryGetValue("bear", out var bearBot))
{
if (bearBot is not null && bearBot.FirstNames!.Any(x => x == playerName))
{
bearBot.FirstNames!.Add(playerName);
}
}
if (bots.TryGetValue("bear", out var usecBot))
{
if (usecBot is not null && usecBot.FirstNames!.Any(x => x == playerName))
{
usecBot.FirstNames!.Add(playerName);
}
}
}
}
@@ -525,10 +469,7 @@ public class GameController(
/// <param name="fullProfile">Profile to check for dialog in</param>
private void CheckForAndRemoveUndefinedDialogues(SptProfile fullProfile)
{
if (fullProfile.DialogueRecords!.TryGetValue("undefined", out var _))
{
fullProfile.DialogueRecords.Remove("undefined");
}
if (fullProfile.DialogueRecords!.TryGetValue("undefined", out _)) fullProfile.DialogueRecords.Remove("undefined");
}
/// <summary>
@@ -540,7 +481,7 @@ public class GameController(
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Profile made with: {fullProfile.SptData?.Version}");
_logger.Debug($"Server version: {(ProgramStatics.SPT_VERSION()) ?? _coreConfig.SptVersion} {ProgramStatics.COMMIT()}");
_logger.Debug($"Server version: {ProgramStatics.SPT_VERSION() ?? _coreConfig.SptVersion} {ProgramStatics.COMMIT()}");
_logger.Debug($"Debug enabled: {ProgramStatics.DEBUG()}");
_logger.Debug($"Mods enabled: {ProgramStatics.MODS()}");
}
+8 -43
View File
@@ -69,15 +69,12 @@ public class HealthController(
}
// Resource in medkit is spent, delete it
if (healingItemToUse.Upd.MedKit.HpResource <= 0)
{
_inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
}
if (healingItemToUse.Upd.MedKit.HpResource <= 0) _inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
var healingItemDbDetails = _itemHelper.GetItem(healingItemToUse.Template);
var healItemEffectDetails = healingItemDbDetails.Value.Properties.EffectsDamage;
BodyPartHealth bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part.ToString());
var bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part.ToString());
if (bodyPartToHeal is null)
{
_logger.Warning($"Player: {sessionID} Tried to heal a non-existent body part: {request.Part}");
@@ -98,10 +95,8 @@ public class HealthController(
{
// Check if healing item removes the effect on limb
if (!healItemEffectDetails.TryGetValue(Enum.Parse<DamageEffectType>(effectKey), out var matchingEffectFromHealingItem))
{
// Healing item doesn't have matching effect, it doesn't remove the effect
continue;
}
// Adjust limb heal amount based on if its fixing an effect (request.count is TOTAL cost of hp resource on heal item, NOT amount to heal limb)
amountToHealLimb -= (int)(matchingEffectFromHealingItem.Cost ?? 0);
@@ -113,10 +108,7 @@ public class HealthController(
bodyPartToHeal.Health.Current += amountToHealLimb;
// Ensure we've not healed beyond the limbs max hp
if (bodyPartToHeal.Health.Current > bodyPartToHeal.Health.Maximum)
{
bodyPartToHeal.Health.Current = bodyPartToHeal.Health.Maximum;
}
if (bodyPartToHeal.Health.Current > bodyPartToHeal.Health.Maximum) bodyPartToHeal.Health.Current = bodyPartToHeal.Health.Maximum;
return output;
}
@@ -139,13 +131,11 @@ public class HealthController(
var itemToConsume = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
if (itemToConsume is null)
{
// Item not found, very bad
return _httpResponseUtil.AppendErrorToOutput(
output,
_localisationService.GetText("health-unable_to_find_item_to_consume", request.Item)
);
}
var consumedItemMaxResource = _itemHelper.GetItem(itemToConsume.Template).Value.Properties.MaxResource;
if (consumedItemMaxResource > 1)
@@ -154,22 +144,15 @@ public class HealthController(
_itemHelper.AddUpdObjectToItem(itemToConsume);
if (itemToConsume.Upd.FoodDrink is null)
{
itemToConsume.Upd.FoodDrink = new UpdFoodDrink { HpPercent = consumedItemMaxResource - request.Count };
}
else
{
itemToConsume.Upd.FoodDrink.HpPercent -= request.Count;
}
resourceLeft = itemToConsume.Upd.FoodDrink.HpPercent.Value;
}
// Remove item from inventory if resource has dropped below threshold
if (consumedItemMaxResource == 1 || resourceLeft < 1)
{
_inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
}
if (consumedItemMaxResource == 1 || resourceLeft < 1) _inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
// Check what effect eating item has and handle
var foodItemDbDetails = _itemHelper.GetItem(itemToConsume.Template).Value;
@@ -198,14 +181,10 @@ public class HealthController(
OffraidEatRequestData request)
{
if (foodIsSingleUse)
{
// Apply whole value from passed in parameter
bodyValue.Current += consumptionDetails.Value;
}
else
{
bodyValue.Current += request.Count;
}
// Ensure current never goes over max
if (bodyValue.Current > bodyValue.Maximum)
@@ -216,10 +195,7 @@ public class HealthController(
}
// Same as above but for the lower bound
if (bodyValue.Current < 0)
{
bodyValue.Current = 0;
}
if (bodyValue.Current < 0) bodyValue.Current = 0;
}
/// <summary>
@@ -248,10 +224,7 @@ public class HealthController(
};
_paymentService.PayMoney(pmcData, payMoneyRequest, sessionID, output);
if (output.Warnings.Count > 0)
{
return output;
}
if (output.Warnings.Count > 0) return output;
foreach (var bodyPartKvP in healthTreatmentRequest.Difference.BodyParts.GetAllPropsAsDict())
{
@@ -261,25 +234,17 @@ public class HealthController(
// Bodypart healing is chosen when part request hp is above 0
if (partRequest.Health > 0)
{
// Heal bodypart
profilePart.Health.Current = profilePart.Health.Maximum;
}
// Check for effects to remove
if (partRequest.Effects?.Count > 0)
{
// Found some, loop over them and remove from pmc profile
foreach (var effect in partRequest.Effects)
{
pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Remove(effect);
}
foreach (var effect in partRequest.Effects) pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Remove(effect);
// Remove empty effect object
if (pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Count == 0)
{
pmcData.Health.BodyParts[bodyPartKvP.Key].Effects = null;
}
if (pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Count == 0) pmcData.Health.BodyParts[bodyPartKvP.Key].Effects = null;
}
}
+39 -138
View File
@@ -54,7 +54,7 @@ public class HideoutController(
HideoutAreas.AIR_FILTERING,
HideoutAreas.WATER_COLLECTOR,
HideoutAreas.GENERATOR,
HideoutAreas.BITCOIN_FARM,
HideoutAreas.BITCOIN_FARM
];
public void StartUpgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
@@ -87,13 +87,9 @@ public class HideoutController(
item.inventoryItem.Upd.StackObjectsCount is not null &&
item.inventoryItem.Upd.StackObjectsCount > item.requestedItem.Count
)
{
item.inventoryItem.Upd.StackObjectsCount -= item.requestedItem.Count;
}
else
{
_inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionID, output);
}
}
// Construction time management
@@ -122,10 +118,7 @@ public class HideoutController(
var ctime = hideoutDataDb.Stages[(profileHideoutArea.Level + 1).ToString()].ConstructionTime;
if (ctime > 0)
{
if (_profileHelper.IsDeveloperAccount(sessionID))
{
ctime = 40;
}
if (_profileHelper.IsDeveloperAccount(sessionID)) ctime = 40;
var timestamp = _timeUtil.GetTimeStamp();
@@ -168,16 +161,11 @@ public class HideoutController(
var hideoutStage = hideoutData.Stages[profileHideoutArea.Level.ToString()];
var bonuses = hideoutStage.Bonuses;
if (bonuses?.Count > 0)
{
foreach (var bonus in bonuses)
{
_hideoutHelper.ApplyPlayerUpgradesBonuses(pmcData, bonus);
}
}
// Upgrade includes a container improvement/addition
if (!string.IsNullOrEmpty(hideoutStage?.Container))
{
AddContainerImprovementToProfile(
output,
sessionID,
@@ -186,22 +174,17 @@ public class HideoutController(
hideoutData,
hideoutStage
);
}
// Upgrading water collector / med station
if (
profileHideoutArea.Type == HideoutAreas.WATER_COLLECTOR ||
profileHideoutArea.Type == HideoutAreas.MEDSTATION
)
{
SetWallVisibleIfPrereqsMet(pmcData);
}
// Cleanup temporary buffs/debuffs from wall if complete
if (profileHideoutArea.Type == HideoutAreas.EMERGENCY_WALL && profileHideoutArea.Level == 6)
{
_hideoutHelper.RemoveHideoutWallBuffsAndDebuffs(hideoutData, pmcData);
}
// Add Skill Points Per Area Upgrade
_profileHelper.AddSkillPointsToPlayer(
@@ -218,10 +201,7 @@ public class HideoutController(
if (medStation?.Level >= 1 && waterCollector?.Level >= 1)
{
var wall = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.EMERGENCY_WALL);
if (wall?.Level == 0)
{
wall.Level = 3;
}
if (wall?.Level == 0) wall.Level = 3;
}
}
@@ -230,27 +210,21 @@ public class HideoutController(
{
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
if (!pmcData.Inventory.HideoutAreaStashes.ContainsKey(dbHideoutArea.Type.ToString()))
{
pmcData.Inventory.HideoutAreaStashes[dbHideoutArea.Type.ToString()] = dbHideoutArea.Id;
}
// Add/upgrade stash item in player inventory
AddUpdateInventoryItemToProfile(sessionID, pmcData, dbHideoutArea, hideoutStage);
// Edge case, add/update `stand1/stand2/stand3` children
if (dbHideoutArea.Type == HideoutAreas.EQUIPMENT_PRESETS_STAND)
{
// Can have multiple 'standx' children depending on upgrade level
AddMissingPresetStandItemsToProfile(sessionID, hideoutStage, pmcData, dbHideoutArea, output);
}
// Dont inform client when upgraded area is hall of fame or equipment stand, BSG doesn't inform client this specifc upgrade has occurred
// will break client if sent
List<HideoutAreas> check = [HideoutAreas.PLACE_OF_FAME];
if (!check.Contains(dbHideoutArea.Type ?? HideoutAreas.NOTSET))
{
AddContainerUpgradeToClientOutput(sessionID, dbHideoutArea.Type, dbHideoutArea, hideoutStage, output);
}
// Some hideout areas (Gun stand) have child areas linked to it
var childDbArea = _databaseService
@@ -260,9 +234,7 @@ public class HideoutController(
{
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
if (pmcData.Inventory.HideoutAreaStashes.GetValueOrDefault(childDbArea.Type.ToString()) is null)
{
pmcData.Inventory.HideoutAreaStashes[childDbArea.Type.ToString()] = childDbArea.Id;
}
// Set child area level to same as parent area
pmcData.Hideout.Areas.FirstOrDefault((hideoutArea) => hideoutArea.Type == childDbArea.Type).Level =
@@ -297,15 +269,13 @@ public class HideoutController(
ItemEventRouterResponse output)
{
if (output.ProfileChanges[sessionID].ChangedHideoutStashes is null)
{
output.ProfileChanges[sessionID].ChangedHideoutStashes = new Dictionary<string, HideoutStashItem>();
}
// Inform client of changes
output.ProfileChanges[sessionID].ChangedHideoutStashes[areaType.ToString()] = new HideoutStashItem
{
Id = hideoutDbData.Id,
Template = hideoutStage.Container,
Template = hideoutStage.Container
};
}
@@ -370,7 +340,7 @@ public class HideoutController(
Id = item.inventoryItem.Id,
Template = item.inventoryItem.Template,
Upd = item.inventoryItem.Upd
},
}
];
_inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionID, output);
@@ -445,15 +415,13 @@ public class HideoutController(
ItemWithModsToAdd = [itemToReturn.ConvertToItem()],
FoundInRaid = itemToReturn.Upd?.SpawnedInSession,
Callback = null,
UseSortingTable = false,
UseSortingTable = false
};
_inventoryHelper.AddItemToStash(sessionID, request, pmcData, output);
if (output.Warnings?.Count > 0)
{
// Adding to stash failed, drop out - don't remove item from hideout area slot
return output;
}
// Remove items from slot, locationIndex remains
var hideoutSlotIndex = hideoutArea.Slots.FindIndex((slot) => slot.LocationIndex == slotIndexToRemove);
@@ -510,18 +478,12 @@ public class HideoutController(
// Handle tools not having a `count`, but always only requiring 1
var requiredCount = requirement.Count ?? 1;
if (requiredCount <= 0)
{
continue;
}
if (requiredCount <= 0) continue;
_inventoryHelper.RemoveItemByCount(pmcData, itemToDelete.Id, requiredCount, sessionID, output);
// Tools don't have a count
if (requirement.Type != "Tool")
{
requirement.Count -= (int)itemToDelete.Count;
}
if (requirement.Type != "Tool") requirement.Count -= (int)itemToDelete.Count;
}
return output;
@@ -546,13 +508,9 @@ public class HideoutController(
}
if (inventoryItem.Upd?.StackObjectsCount is not null && inventoryItem.Upd.StackObjectsCount > requestedItem.Count)
{
inventoryItem.Upd.StackObjectsCount -= requestedItem.Count;
}
else
{
_inventoryHelper.RemoveItem(pmcData, requestedItem.Id, sessionID, output);
}
}
var recipe = _databaseService.GetHideout().Production?.ScavRecipes?.FirstOrDefault(r => r.Id == body.RecipeId);
@@ -592,10 +550,7 @@ public class HideoutController(
private double? GetScavCaseTime(PmcData pmcData, double? productionTime)
{
var fenceLevel = _fenceService.GetFenceInfo(pmcData);
if (fenceLevel is null)
{
return productionTime;
}
if (fenceLevel is null) return productionTime;
return productionTime * fenceLevel.ScavCaseTimeModifier;
}
@@ -661,10 +616,7 @@ public class HideoutController(
foreach (var production in productionDict)
{
// Skip undefined production objects
if (production.Value is null)
{
continue;
}
if (production.Value is null) continue;
// Production or ScavCase
if (production.Value.RecipeId == request.RecipeId)
@@ -723,11 +675,11 @@ public class HideoutController(
if (rewardIsStackable ?? false)
{
// Create root item
Item rewardToAdd = new Item
var rewardToAdd = new Item
{
Id = _hashUtil.Generate(),
Template = recipe.EndProduct,
Upd = new Upd { StackObjectsCount = recipe.Count },
Upd = new Upd { StackObjectsCount = recipe.Count }
};
// Split item into separate items with acceptable stack sizes
@@ -739,50 +691,39 @@ public class HideoutController(
// Not stackable, may have to send send multiple of reward
// Add the first reward item to array when not a preset (first preset added above earlier)
if (!rewardIsPreset)
{
itemAndChildrenToSendToPlayer.Add([new Item { Id = _hashUtil.Generate(), Template = recipe.EndProduct }]);
}
if (!rewardIsPreset) itemAndChildrenToSendToPlayer.Add([new Item { Id = _hashUtil.Generate(), Template = recipe.EndProduct }]);
// Add multiple of item if recipe requests it
// Start index at one so we ignore first item in array
var countOfItemsToReward = recipe.Count;
for (var index = 1; index < countOfItemsToReward; index++)
{
List<Item> itemAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(itemAndChildrenToSendToPlayer.FirstOrDefault()));
var itemAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(itemAndChildrenToSendToPlayer.FirstOrDefault()));
itemAndChildrenToSendToPlayer.AddRange([itemAndMods]);
}
}
// Recipe has an `isEncoded` requirement for reward(s), Add `RecodableComponent` property
if (recipe.IsEncoded ?? false)
{
foreach (var reward in itemAndChildrenToSendToPlayer)
{
_itemHelper.AddUpdObjectToItem(reward.FirstOrDefault());
reward.FirstOrDefault().Upd.RecodableComponent = new UpdRecodableComponent { IsEncoded = true };
}
}
// Build an array of the tools that need to be returned to the player
List<List<Item>> toolsToSendToPlayer = [];
var hideoutProduction = pmcData.Hideout.Production[prodId];
if (hideoutProduction.SptRequiredTools?.Count > 0)
{
foreach (var tool in hideoutProduction.SptRequiredTools)
{
toolsToSendToPlayer.Add([tool]);
}
}
// Check if the recipe is the same as the last one - get bonus when crafting same thing multiple times
var area = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == recipe.AreaType);
if (area is not null && request.RecipeId != area.LastRecipe)
{
// 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
craftingExpAmount += _hideoutConfig.ExpCraftAmount; // Default is 10
}
// Update variable with time spent crafting item(s)
// 1 point per 8 hours of crafting
@@ -814,34 +755,28 @@ public class HideoutController(
foreach (var toolItem in toolsToSendToPlayer)
{
// Note: FIR state will be based on the first item's SpawnedInSession property per item group
AddItemsDirectRequest addToolsRequest = new AddItemsDirectRequest
var addToolsRequest = new AddItemsDirectRequest
{
ItemsWithModsToAdd = [toolItem],
FoundInRaid = toolItem[0].Upd?.SpawnedInSession ?? false,
UseSortingTable = false,
Callback = null,
Callback = null
};
_inventoryHelper.AddItemsToStash(sessionID, addToolsRequest, pmcData, output);
if (output.Warnings?.Count > 0)
{
return;
}
if (output.Warnings?.Count > 0) return;
}
// Add the crafting result to the stash, marked as FiR
AddItemsDirectRequest addItemsRequest = new AddItemsDirectRequest
var addItemsRequest = new AddItemsDirectRequest
{
ItemsWithModsToAdd = itemAndChildrenToSendToPlayer,
FoundInRaid = true,
UseSortingTable = false,
Callback = null,
Callback = null
};
_inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
if (output.Warnings?.Count > 0)
{
return;
}
if (output.Warnings?.Count > 0) return;
// - increment skill point for crafting
// - delete the production in profile Hideout.Production
@@ -861,10 +796,7 @@ public class HideoutController(
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Crafting, craftingExpAmount);
var intellectAmountToGive = 0.5 * Math.Round((double)(craftingExpAmount / 15));
if (intellectAmountToGive > 0)
{
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectAmountToGive);
}
if (intellectAmountToGive > 0) _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectAmountToGive);
}
area.LastRecipe = request.RecipeId;
@@ -879,36 +811,29 @@ public class HideoutController(
// Continious recipies need the craft time refreshed as it gets created once on initial craft and stays the same regardless of what
// production.json is set to
if (recipe.Continuous ?? false)
{
pmcData.Hideout.Production[prodId].ProductionTime = _hideoutHelper.GetAdjustedCraftTimeWithSkills(
pmcData,
recipe.Id,
true
);
}
// Flag normal (non continious) crafts as complete
if (!recipe.Continuous ?? false)
{
pmcData.Hideout.Production[prodId].InProgress = false;
}
if (!recipe.Continuous ?? false) pmcData.Hideout.Production[prodId].InProgress = false;
}
private TaskConditionCounter GetHoursCraftingTaskConditionCounter(PmcData pmcData, HideoutProduction recipe)
{
if (!pmcData.TaskConditionCounters.TryGetValue(HideoutController.NameTaskConditionCountersCraftingId, out var _))
{
if (!pmcData.TaskConditionCounters.TryGetValue(NameTaskConditionCountersCraftingId, out _))
// Doesn't exist, create
pmcData.TaskConditionCounters[HideoutController.NameTaskConditionCountersCraftingId] = new TaskConditionCounter
pmcData.TaskConditionCounters[NameTaskConditionCountersCraftingId] = new TaskConditionCounter
{
Id = recipe.Id,
Type = HideoutController.NameTaskConditionCountersCraftingId,
Type = NameTaskConditionCountersCraftingId,
SourceId = "CounterCrafting",
Value = 0,
Value = 0
};
}
return pmcData.TaskConditionCounters[HideoutController.NameTaskConditionCountersCraftingId];
return pmcData.TaskConditionCounters[NameTaskConditionCountersCraftingId];
}
private void HandleScavCase(string sessionID, PmcData pmcData, HideoutTakeProductionRequestData request, ItemEventRouterResponse output)
@@ -916,14 +841,12 @@ public class HideoutController(
var ongoingProductions = pmcData.Hideout.Production;
string? prodId = null;
foreach (var production in ongoingProductions)
{
// Production or ScavCase
if ((production.Value).RecipeId == request.RecipeId)
if (production.Value.RecipeId == request.RecipeId)
{
prodId = production.Key; // Set to objects key
break;
}
}
if (prodId == null)
{
@@ -942,19 +865,16 @@ public class HideoutController(
// Create rewards for scav case
var scavCaseRewards = _scavCaseRewardGenerator.Generate(request.RecipeId);
AddItemsDirectRequest addItemsRequest = new AddItemsDirectRequest
var addItemsRequest = new AddItemsDirectRequest
{
ItemsWithModsToAdd = scavCaseRewards,
FoundInRaid = true,
Callback = null,
UseSortingTable = false,
UseSortingTable = false
};
_inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
if (output.Warnings?.Count > 0)
{
return;
}
if (output.Warnings?.Count > 0) return;
// Remove the old production from output object before its sent to client
output.ProfileChanges[sessionID].Production.Remove(request.RecipeId);
@@ -981,7 +901,6 @@ public class HideoutController(
var qteDb = _databaseService.GetHideout().Qte;
var relevantQte = qteDb.FirstOrDefault(qte => qte.Id == request.Id);
foreach (var outcome in request.Results)
{
if (outcome)
{
// Success
@@ -994,17 +913,10 @@ public class HideoutController(
pmcData.Health.Energy.Current += relevantQte.Results[QteEffectType.singleFailEffect].Energy;
pmcData.Health.Hydration.Current += relevantQte.Results[QteEffectType.singleFailEffect].Hydration;
}
}
if (pmcData.Health.Energy.Current < 1)
{
pmcData.Health.Energy.Current = 1;
}
if (pmcData.Health.Energy.Current < 1) pmcData.Health.Energy.Current = 1;
if (pmcData.Health.Hydration.Current < 1)
{
pmcData.Health.Hydration.Current = 1;
}
if (pmcData.Health.Hydration.Current < 1) pmcData.Health.Hydration.Current = 1;
HandleMusclePain(pmcData, relevantQte.Results[QteEffectType.finishEffect]);
}
@@ -1021,7 +933,7 @@ public class HideoutController(
pmcData.Health.BodyParts["Chest"].Effects ??= new Dictionary<string, BodyPartEffectProperties>();
pmcData.Health.BodyParts["Chest"].Effects["MildMusclePain"] = new BodyPartEffectProperties
{
Time = finishEffect.RewardEffects.FirstOrDefault().Time, // TODO - remove hard coded access, get value properly
Time = finishEffect.RewardEffects.FirstOrDefault().Time // TODO - remove hard coded access, get value properly
};
return;
@@ -1034,7 +946,7 @@ public class HideoutController(
pmcData.Health.BodyParts["Chest"].Effects["SevereMusclePain"] = new BodyPartEffectProperties
{
Time = finishEffect.RewardEffects.FirstOrDefault().Time,
Time = finishEffect.RewardEffects.FirstOrDefault().Time
};
}
}
@@ -1086,13 +998,9 @@ public class HideoutController(
item.inventoryItem.Upd.StackObjectsCount is not null &&
item.inventoryItem.Upd.StackObjectsCount > item.requestedItem.Count
)
{
item.inventoryItem.Upd.StackObjectsCount -= item.requestedItem.Count;
}
else
{
_inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionId, output);
}
}
var profileHideoutArea = pmcData.Hideout.Areas.FirstOrDefault(x => x.Type == request.AreaType);
@@ -1115,17 +1023,14 @@ public class HideoutController(
var improvements = hideoutDbData.Stages[profileHideoutArea.Level.ToString()].Improvements;
var timestamp = _timeUtil.GetTimeStamp();
if (output.ProfileChanges[sessionId].Improvements is null)
{
output.ProfileChanges[sessionId].Improvements = new Dictionary<string, HideoutImprovement>();
}
if (output.ProfileChanges[sessionId].Improvements is null) output.ProfileChanges[sessionId].Improvements = new Dictionary<string, HideoutImprovement>();
foreach (var improvement in improvements)
{
var improvementDetails = new HideoutImprovement
{
Completed = false,
ImproveCompleteTimestamp = (long)(timestamp + improvement.ImprovementTime),
ImproveCompleteTimestamp = (long)(timestamp + improvement.ImprovementTime)
};
output.ProfileChanges[sessionId].Improvements[improvement.Id] = improvementDetails;
@@ -1232,7 +1137,7 @@ public class HideoutController(
Id = standId,
Template = ItemTpl.INVENTORY_DEFAULT,
ParentId = equipmentPresetHideoutArea.Id,
SlotId = mannequinSlot.Name,
SlotId = mannequinSlot.Name
};
pmcData.Inventory.Items.Add(mannequinToAdd);
@@ -1245,7 +1150,7 @@ public class HideoutController(
)
.Template, // Same pocket tpl as players profile (unheard get bigger, matching pockets etc)
ParentId = standId,
SlotId = "Pockets",
SlotId = "Pockets"
};
pmcData.Inventory.Items.Add(mannequinPocketItemToAdd);
output.ProfileChanges[sessionId].Items.NewItems.Add(mannequinToAdd);
@@ -1290,16 +1195,12 @@ public class HideoutController(
public void Update()
{
foreach (var sessionID in _saveServer.GetProfiles())
{
if (sessionID.Value.CharacterData.PmcData.Hideout is not null &&
_profileActivityService.ActiveWithinLastMinutes(
sessionID.Key,
_hideoutConfig.UpdateProfileHideoutWhenActiveWithinMinutes
)
)
{
_hideoutHelper.UpdatePlayerHideout(sessionID.Key);
}
}
}
}
@@ -43,9 +43,7 @@ public class InRaidController(
// If equipment match overwrite existing data from update to date raid data for scavenger screen to work correctly.
// otherwise Scav inventory will be overwritten and break scav regeneration, breaking profile.
if (serverScavProfile.Inventory.Equipment == offRaidProfileData.Inventory.Equipment)
{
serverScavProfile.Inventory.Items = offRaidProfileData.Inventory.Items;
}
}
/// <summary>
+28 -111
View File
@@ -86,12 +86,8 @@ public class InsuranceController(
var profileInsuranceDetails = _saveServer.GetProfile(sessionId).InsuranceList;
if (profileInsuranceDetails.Count > 0)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Found {profileInsuranceDetails.Count} insurance packages in profile {sessionId}");
}
}
return profileInsuranceDetails.Where(insured => insuranceTime >= insured.ScheduledTime).ToList();
}
@@ -105,12 +101,10 @@ public class InsuranceController(
*/
protected void ProcessInsuredItems(List<Insurance> insuranceDetails, string sessionId)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Processing {insuranceDetails.Count} insurance packages, which includes a total of: {CountAllInsuranceItems(insuranceDetails)} items, in profile: {sessionId}"
);
}
// Iterate over each of the insurance packages.
foreach (var insured in insuranceDetails)
@@ -170,10 +164,7 @@ public class InsuranceController(
)
.ToList();
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}");
}
/**
@@ -198,10 +189,7 @@ public class InsuranceController(
);
// Process all items that are not attached, attachments; those are handled separately, by value.
if (hasRegularItems)
{
ProcessRegularItems(insured, toDelete, parentAttachmentsMap);
}
if (hasRegularItems) ProcessRegularItems(insured, toDelete, parentAttachmentsMap);
// Process attached, attachments, by value, only if there are any.
if (parentAttachmentsMap.Count > 0)
@@ -215,12 +203,8 @@ public class InsuranceController(
// Log the number of items marked for deletion, if any
if (!toDelete.Any())
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Marked {toDelete.Count} items for deletion from insurance.");
}
}
return toDelete;
}
@@ -253,7 +237,7 @@ public class InsuranceController(
{
insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template,
parentId = insuredItem.ParentId,
parentId = insuredItem.ParentId
}
)
);
@@ -273,7 +257,7 @@ public class InsuranceController(
new
{
insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template,
insuredItemTpl = insuredItem.Template
}
)
);
@@ -293,7 +277,7 @@ public class InsuranceController(
{
insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template,
parentId = insuredItem.ParentId,
parentId = insuredItem.ParentId
}
)
);
@@ -304,10 +288,7 @@ public class InsuranceController(
// Update (or add to) the main-parent to attachments map.
if (mainParentToAttachmentsMap.ContainsKey(mainParent.Id))
{
if (mainParentToAttachmentsMap.TryGetValue(mainParent.Id, out var parent))
{
parent.Add(insuredItem);
}
if (mainParentToAttachmentsMap.TryGetValue(mainParent.Id, out var parent)) parent.Add(insuredItem);
}
else
{
@@ -344,24 +325,14 @@ public class InsuranceController(
// attachment on the main-parent. For example, if the attachment is a stock, we need to check to see if
// it's moddable in the upper receiver (attachment/parent), which is attached to the gun (main-parent).
if (attachment.ParentId is not null)
{
if (itemsMap.TryGetValue(attachment.ParentId, out var directParentItem))
{
attachmentParentItem = directParentItem;
}
}
if (_itemHelper.IsRaidModdable(attachment, attachmentParentItem) ?? false)
{
moddableAttachments.Add(attachment);
}
if (_itemHelper.IsRaidModdable(attachment, attachmentParentItem) ?? false) moddableAttachments.Add(attachment);
}
// If any moddable attachments remain, add them to the updated map.
if (moddableAttachments.Count > 0)
{
updatedMap.TryAdd(map.Key, moddableAttachments);
}
if (moddableAttachments.Count > 0) updatedMap.TryAdd(map.Key, moddableAttachments);
}
return updatedMap;
@@ -382,10 +353,7 @@ public class InsuranceController(
foreach (var insuredItem in insured.Items)
{
// Skip if the item is an attachment. These are handled separately.
if (_itemHelper.IsAttachmentAttached(insuredItem))
{
continue;
}
if (_itemHelper.IsAttachmentAttached(insuredItem)) continue;
// Roll for item deletion
var itemRoll = RollForDelete(insured.TraderId, insuredItem);
@@ -401,10 +369,7 @@ public class InsuranceController(
insured.Items,
insuredItem.Id
);
foreach (var item in itemAndChildren)
{
toDelete.Add(item.Id);
}
foreach (var item in itemAndChildren) toDelete.Add(item.Id);
// Remove the parent (and its children) from the parentAttachmentsMap.
parentAttachmentsMap.Remove(insuredItem.Id);
@@ -433,18 +398,12 @@ public class InsuranceController(
{
// Skip processing if parentId is already marked for deletion, as all attachments for that parent will
// already be marked for deletion as well.
if (toDelete.Contains(parentObj.Key))
{
continue;
}
if (toDelete.Contains(parentObj.Key)) continue;
// Log the parent item's name.
itemsMap.TryGetValue(parentObj.Key, out var parentItem);
var parentName = _itemHelper.GetItemName(parentItem.Template);
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Processing attachments of parent {parentName}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Processing attachments of parent {parentName}");
// Process the attachments for this individual parent item.
ProcessAttachmentByParent(parentObj.Value, insuredTraderId, toDelete);
@@ -473,25 +432,17 @@ public class InsuranceController(
// Create prob array and add all attachments with rouble price as the weight
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner);
foreach (var attachmentTpl in weightedAttachmentByPrice)
{
attachmentsProbabilityArray.Add(
new ProbabilityObject<string, double?>(attachmentTpl.Key, attachmentTpl.Value, null)
);
}
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked
var attachmentIdsToRemove = attachmentsProbabilityArray.Draw((int)countOfAttachmentsToRemove, false);
foreach (var attachmentId in attachmentIdsToRemove)
{
toDelete.Add(attachmentId);
}
foreach (var attachmentId in attachmentIdsToRemove) toDelete.Add(attachmentId);
LogAttachmentsBeingRemoved(attachmentIdsToRemove, attachments, weightedAttachmentByPrice);
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Number of attachments to be deleted: {attachmentIdsToRemove.Count}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Number of attachments to be deleted: {attachmentIdsToRemove.Count}");
}
private void LogAttachmentsBeingRemoved(List<string> attachmentIdsToRemove, List<Item> attachments, Dictionary<string, double> attachmentPrices)
@@ -500,12 +451,10 @@ public class InsuranceController(
foreach (var attachmentId in attachmentIdsToRemove)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Attachment {index} Id: {attachmentId} Tpl: {attachments.FirstOrDefault((x) => x.Id == attachmentId)?.Template} - " +
$"Price: {attachmentPrices[attachmentId]}"
);
}
index++;
}
}
@@ -518,10 +467,7 @@ public class InsuranceController(
foreach (var attachment in attachments)
{
var price = _ragfairPriceService.GetDynamicItemPrice(attachment.Template, Money.ROUBLES);
if (price is not null)
{
result[attachment.Id] = Math.Round(price ?? 0);
}
if (price is not null) result[attachment.Id] = Math.Round(price ?? 0);
}
_weightedRandomHelper.ReduceWeightValues(result);
@@ -533,10 +479,7 @@ public class InsuranceController(
{
var removeCount = 0;
if (_randomUtil.GetChance100(_insuranceConfig.ChanceNoAttachmentsTakenPercent))
{
return removeCount;
}
if (_randomUtil.GetChance100(_insuranceConfig.ChanceNoAttachmentsTakenPercent)) return removeCount;
// Get attachments count above or equal to price set in config
return weightedAttachmentByPrice
@@ -564,19 +507,13 @@ public class InsuranceController(
// Map is labs + insurance is disabled in base.json
if (IsMapLabsAndInsuranceDisabled(insurance))
{
// Trader has labs-specific messages
// Wipe out returnable items
HandleLabsInsurance(traderDialogMessages, insurance);
}
else if (insurance.Items?.Count == 0)
{
// Not labs and no items to return
if (traderDialogMessages.TryGetValue("insuranceFailed", out var insuranceFailedTemplates))
{
insurance.MessageTemplateId = _randomUtil.GetArrayValue(insuranceFailedTemplates);
}
}
// Send the insurance message
_mailSendService.SendLocalisedNpcMessageToPlayer(
@@ -592,9 +529,8 @@ public class InsuranceController(
private bool IsMapLabsAndInsuranceDisabled(Insurance insurance, string labsId = "laboratory")
{
return (insurance.SystemData?.Location?.ToLower() == labsId
&& !(_databaseService.GetLocation(labsId)?.Base?.Insurance.GetValueOrDefault(false) ?? false)
);
return insurance.SystemData?.Location?.ToLower() == labsId &&
!(_databaseService.GetLocation(labsId)?.Base?.Insurance.GetValueOrDefault(false) ?? false);
}
/**
@@ -618,10 +554,7 @@ public class InsuranceController(
private bool? RollForDelete(string traderId, Item? insuredItem = null)
{
var trader = _traderHelper.GetTraderById(traderId);
if (trader is null)
{
return null;
}
if (trader is null) return null;
const int maxRoll = 9999;
const int conversionFactor = 100;
@@ -633,10 +566,8 @@ public class InsuranceController(
// Log the roll with as much detail as possible.
var itemName = insuredItem is not null ? $"{_itemHelper.GetItemName(insuredItem.Template)}" : "";
var status = roll ? "Delete" : "Keep";
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Rolling {itemName} with {trader} - Return {traderReturnChance}% - Roll: {returnChance} - Status: {status}");
}
return roll;
}
@@ -661,7 +592,6 @@ public class InsuranceController(
// Get price of all items being insured, add to 'itemsToPay'
foreach (var key in body.Items)
{
itemsToPay.Add(
new IdWithCount
{
@@ -673,7 +603,6 @@ public class InsuranceController(
)
}
);
}
var options = new ProcessBuyTradeRequestData
{
@@ -688,10 +617,7 @@ public class InsuranceController(
// pay for the item insurance
_paymentService.PayMoney(pmcData, options, sessionId, output);
if (output.Warnings?.Count > 0)
{
return output;
}
if (output.Warnings?.Count > 0) return output;
// add items to InsuredItems list once money has been paid
pmcData.InsuredItems ??= [];
@@ -699,10 +625,7 @@ public class InsuranceController(
{
pmcData.InsuredItems.Add(new InsuredItem { TId = body.TransactionId, ItemId = inventoryItemsHash[key].Id });
// If Item is Helmet or Body Armour -> Handle insurance of soft inserts
if (_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template))
{
InsureSoftInserts(inventoryItemsHash[key], pmcData, body);
}
if (_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template)) InsureSoftInserts(inventoryItemsHash[key], pmcData, body);
}
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Charisma, itemsToInsureCount * 0.01);
@@ -726,11 +649,8 @@ public class InsuranceController(
foreach (var softInsertSlot in softInsertSlots)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"SoftInsertSlots: {softInsertSlot.SlotId}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"SoftInsertSlots: {softInsertSlot.SlotId}");
pmcData.InsuredItems.Add(new InsuredItem { TId = body.TransactionId, ItemId = softInsertSlot.Id });
}
}
@@ -761,10 +681,7 @@ public class InsuranceController(
// Ensure hash has item in it
if (!inventoryItemsHash.ContainsKey(itemId))
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Item with id: {itemId} missing from player inventory, skipping");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Item with id: {itemId} missing from player inventory, skipping");
continue;
}
@@ -83,9 +83,7 @@ public class InventoryController(
// Item is moving into or out of place of fame dog tag slot
if (moveRequest.To?.Container != null &&
(moveRequest.To.Container.StartsWith("dogtag") || originalLocationSlotId.StartsWith("dogtag")))
{
_hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData);
}
}
else
{
@@ -155,7 +153,7 @@ public class InventoryController(
_logger.Success($"Set trader {mailEvent.Entity}: Standing to: {mailEvent.Value}");
break;
case ProfileChangeEventType.ProfileLevel:
pmcData.Info.Experience = (int) mailEvent.Value.Value;
pmcData.Info.Experience = (int)mailEvent.Value.Value;
// Will calculate level below
_traderHelper.ValidateTraderStandingsAndPlayerLevelForProfile(sessionId);
_logger.Success($"Set profile xp to: {mailEvent.Value}");
@@ -347,23 +345,16 @@ public class InventoryController(
inventoryItem.ParentId = change.ParentId;
inventoryItem.SlotId = change.SlotId;
if (change.Location is not null)
{
inventoryItem.Location = change.Location;
}
else
{
inventoryItem.Location = null;
}
}
}
public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData body,
string sessionId)
{
foreach (var id in body.Ids)
{
pmcData.Encyclopedia[id] = true;
}
foreach (var id in body.Ids) pmcData.Encyclopedia[id] = true;
return _eventOutputHolder.GetOutput(sessionId);
}
@@ -373,7 +364,6 @@ public class InventoryController(
{
string? itemId = null;
if (request.FromOwner is not null)
{
try
{
itemId = GetExaminedItemTpl(request, sessionId);
@@ -382,25 +372,17 @@ public class InventoryController(
{
_logger.Error(_localisationService.GetText("inventory-examine_item_does_not_exist", request.Item));
}
}
if (itemId is null)
{
// item template
if (_databaseService.GetItems().ContainsKey(request.Item))
{
itemId = request.Item;
}
}
if (itemId is null)
{
// Player inventory
var target = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
if (target is not null)
{
itemId = target.Template;
}
if (target is not null) itemId = target.Template;
}
if (itemId is not null)
@@ -415,29 +397,22 @@ public class InventoryController(
if (_presetHelper.IsPreset(request.Item)) return _presetHelper.GetBaseItemTpl(request.Item);
if (request.FromOwner.Id == Traders.FENCE)
{
// Get tpl from fence assorts
return _fenceService.GetRawFenceAssorts().Items.FirstOrDefault(x => x.Id == request.Item)?.Template;
}
if (request.FromOwner.Type == "Trader")
{
// Not fence
// get tpl from trader assort
return _databaseService
.GetTrader(request.FromOwner.Id)
.Assort.Items.FirstOrDefault(item => item.Id == request.Item)
?.Template;
}
if (request.FromOwner.Type == "RagFair")
{
// Try to get tplId from items.json first
var item = _itemHelper.GetItem(request.Item);
if (item.Key)
{
return item.Value.Id;
}
if (item.Key) return item.Value.Id;
// Try alternate way of getting offer if first approach fails
var offer = _ragfairOfferService.GetOfferByOfferId(request.Item) ??
@@ -445,20 +420,14 @@ public class InventoryController(
// Try find examine item inside offer items array
var matchingItem = offer.Items.FirstOrDefault(offerItem => offerItem.Id == request.Item);
if (matchingItem is not null)
{
return matchingItem.Template;
}
if (matchingItem is not null) return matchingItem.Template;
// Unable to find item in database or ragfair
_logger.Warning(_localisationService.GetText("inventory-unable_to_find_item", request.Item));
}
// get hideout item
if (request.FromOwner.Type == "HideoutProduction")
{
return request.Item;
}
if (request.FromOwner.Type == "HideoutProduction") return request.Item;
if (request.FromOwner.Type == "Mail")
{
@@ -472,10 +441,7 @@ public class InventoryController(
// get the Id given and get the Template ID from that
var item = message.Items.Data.FirstOrDefault(item => item.Id == request.Item);
if (item is not null)
{
return item.Template;
}
if (item is not null) return item.Template;
}
_logger.Error($"Unable to get item with id: {request.Item}");
@@ -559,10 +525,7 @@ public class InventoryController(
var playerData = pmcData;
// We may be folding data on scav profile, get that profile instead
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
{
playerData = _profileHelper.GetScavProfile(sessionId);
}
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id) playerData = _profileHelper.GetScavProfile(sessionId);
var itemToFold = playerData.Inventory.Items.FirstOrDefault(item => item?.Id == request.Item);
if (itemToFold is null)
@@ -592,10 +555,7 @@ public class InventoryController(
{
// During post-raid scav transfer, the swap may be in the scav inventory
var playerData = pmcData;
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
{
playerData = _profileHelper.GetScavProfile(sessionId);
}
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id) playerData = _profileHelper.GetScavProfile(sessionId);
var itemOne = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item);
if (itemOne is null)
@@ -631,24 +591,16 @@ public class InventoryController(
// Request object has location data, add it in, otherwise remove existing location from object
if (request.To.Location is not null)
{
itemOne.Location = request.To.Location;
}
else
{
itemOne.Location = null;
}
itemTwo.ParentId = request.To2.Id;
itemTwo.SlotId = request.To2.Container;
if (request.To2.Location is not null)
{
itemTwo.Location = request.To2.Location;
}
else
{
itemTwo.Location = null;
}
// Client already informed of inventory locations, nothing for us to do
return _eventOutputHolder.GetOutput(sessionId);
@@ -696,15 +648,11 @@ public class InventoryController(
var sourceStackCount = sourceItem.Upd.StackObjectsCount;
if (sourceStackCount > request.Count)
{
// Source items stack count greater than new desired count
sourceItem.Upd.StackObjectsCount = sourceStackCount - request.Count;
}
else
{
// Moving a full stack onto a smaller stack
sourceItem.Upd.StackObjectsCount = sourceStackCount - 1;
}
destinationItem.Upd ??= new Upd { StackObjectsCount = 1 };
@@ -743,27 +691,19 @@ public class InventoryController(
}
if (destinationItem.Upd?.StackObjectsCount is null)
{
// No stackcount on destination, add one
destinationItem.Upd = new Upd { StackObjectsCount = 1 };
}
if (sourceItem.Upd is null)
{
sourceItem.Upd = new Upd { StackObjectsCount = 1 };
}
else if (sourceItem.Upd.StackObjectsCount is null)
{
// Items pulled out of raid can have no stack count if the stack should be 1
sourceItem.Upd.StackObjectsCount = 1;
}
// Remove FiR status from destination stack when source stack has no FiR but destination does
if (!sourceItem.Upd.SpawnedInSession.GetValueOrDefault(false) &&
destinationItem.Upd.SpawnedInSession.GetValueOrDefault(false))
{
destinationItem.Upd.SpawnedInSession = false;
}
destinationItem.Upd.StackObjectsCount +=
sourceItem.Upd.StackObjectsCount; // Add source stackcount to destination
@@ -45,7 +45,7 @@ public class LauncherController(
BackendUrl = _httpServerHelper.GetBackendUrl(),
Name = _coreConfig.ServerName,
Editions = profiles,
ProfileDescriptions = GetProfileDescriptions(),
ProfileDescriptions = GetProfileDescriptions()
};
}
@@ -83,10 +83,7 @@ public class LauncherController(
foreach (var kvp in _saveServer.GetProfiles())
{
var account = _saveServer.GetProfile(kvp.Key).ProfileInfo;
if (info?.Username == account?.Username)
{
return kvp.Key;
}
if (info?.Username == account?.Username) return kvp.Key;
}
return null;
@@ -95,12 +92,8 @@ public class LauncherController(
public string Register(RegisterData info)
{
foreach (var kvp in _saveServer.GetProfiles())
{
if (info.Username == _saveServer.GetProfile(kvp.Key).ProfileInfo?.Username)
{
return "";
}
}
return CreateAccount(info);
}
@@ -117,7 +110,7 @@ public class LauncherController(
Username = info.Username,
Password = info.Password,
IsWiped = true,
Edition = info.Edition,
Edition = info.Edition
};
_saveServer.CreateProfile(newProfileDetails);
@@ -146,10 +139,7 @@ public class LauncherController(
{
var sessionID = Login(info);
if (!string.IsNullOrEmpty(sessionID))
{
_saveServer.GetProfile(sessionID).ProfileInfo!.Username = info.Change;
}
if (!string.IsNullOrEmpty(sessionID)) _saveServer.GetProfile(sessionID).ProfileInfo!.Username = info.Change;
return sessionID;
}
@@ -158,10 +148,7 @@ public class LauncherController(
{
var sessionID = Login(info);
if (!string.IsNullOrEmpty(sessionID))
{
_saveServer.GetProfile(sessionID).ProfileInfo!.Password = info.Change;
}
if (!string.IsNullOrEmpty(sessionID)) _saveServer.GetProfile(sessionID).ProfileInfo!.Password = info.Change;
return sessionID;
}
@@ -173,10 +160,7 @@ public class LauncherController(
*/
public string? Wipe(RegisterData info)
{
if (!_coreConfig.AllowProfileWipe)
{
return null;
}
if (!_coreConfig.AllowProfileWipe) return null;
var sessionID = Login(info);
@@ -58,7 +58,7 @@ public class LauncherV2Controller(
var casterPropertyValue = propertyValue as ProfileSides;
result[templatesProperty.GetJsonName()] = _localisationService.GetText(casterPropertyValue?.DescriptionLocaleKey!);
}
return result;
}
@@ -82,12 +82,8 @@ public class LauncherV2Controller(
public bool Register(RegisterData info)
{
foreach (var session in _saveServer.GetProfiles())
{
if (info.Username == _saveServer.GetProfile(session.Key).ProfileInfo!.Username)
{
return false;
}
}
CreateAccount(info);
return true;
@@ -104,7 +100,7 @@ public class LauncherV2Controller(
if (sessionId is null)
return false;
_saveServer.GetProfile(sessionId).ProfileInfo!.Password = info.Password;
return true;
}
@@ -117,7 +113,7 @@ public class LauncherV2Controller(
public bool Remove(LoginRequestData info)
{
var sessionId = GetSessionId(info);
return sessionId is not null && _saveServer.RemoveProfile(sessionId);
}
@@ -169,22 +165,22 @@ public class LauncherV2Controller(
IsWiped = true,
Edition = info.Edition
};
_saveServer.CreateProfile(newProfileDetails);
_saveServer.LoadProfile(profileId);
_saveServer.SaveProfile(profileId);
return profileId;
}
protected string GenerateProfileId()
{
var timestamp = _timeUtil.GetTimeStamp();
return FormatID(timestamp, timestamp * _randomUtil.GetInt(1, 1000000));
}
protected string FormatID(long timeStamp, long counter)
{
var timeStampStr = Convert.ToString(timeStamp, 16).PadLeft(8, '0');
@@ -196,13 +192,8 @@ public class LauncherV2Controller(
protected string? GetSessionId(LoginRequestData info)
{
foreach (var profile in _saveServer.GetProfiles())
{
if (info.Username == profile.Value.ProfileInfo!.Username
&& info.Password == profile.Value.ProfileInfo.Password)
{
if (info.Username == profile.Value.ProfileInfo!.Username && info.Password == profile.Value.ProfileInfo.Password)
return profile.Key;
}
}
return null;
}
@@ -18,7 +18,6 @@ public class LocationController(
ICloner _cloner
)
{
/// <summary>
/// Handle client/locations
/// Get all maps base location properties without loot data
@@ -38,10 +37,7 @@ public class LocationController(
var mapBase = kvp.Value.Base;
if (mapBase == null)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Map: {kvp} has no base json file, skipping generation");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Map: {kvp} has no base json file, skipping generation");
continue;
}
@@ -65,10 +61,7 @@ public class LocationController(
/// <returns></returns>
public GetAirdropLootResponse? GetAirDropLoot(GetAirdropLootRequest? request)
{
if (request?.ContainerId is not null)
{
return _airdropService.GenerateCustomAirdropLoot(request);
}
if (request?.ContainerId is not null) return _airdropService.GenerateCustomAirdropLoot(request);
return _airdropService.GenerateAirdropLoot();
}
@@ -50,7 +50,7 @@ public class MatchController(
/// <returns></returns>
public ProfileStatusResponse JoinMatch(MatchGroupStartGameRequest info, string sessionId)
{
ProfileStatusResponse output = new ProfileStatusResponse
var output = new ProfileStatusResponse
{
MaxPveCountExceeded = false,
// get list of players joining into the match
@@ -105,11 +105,9 @@ public class MatchController(
// Set pmcs to difficulty set in pre-raid screen if override in bot config isnt enabled
if (!_pmcConfig.UseDifficultyOverride)
{
_pmcConfig.Difficulty = ConvertDifficultyDropdownIntoBotDifficulty(
request.WavesSettings.BotDifficulty.ToString()
);
}
}
/// <summary>
@@ -120,10 +118,7 @@ public class MatchController(
private string ConvertDifficultyDropdownIntoBotDifficulty(string botDifficulty)
{
// Edge case medium - must be altered
if (botDifficulty.ToLower() == "medium")
{
return "normal";
}
if (botDifficulty.ToLower() == "medium") return "normal";
return botDifficulty;
}
+4 -4
View File
@@ -23,9 +23,9 @@ public class NoteController(
NoteActionData body,
string sessionId)
{
Note newNote = new Note { Time = body.Note.Time, Text = body.Note.Text };
var newNote = new Note { Time = body.Note.Time, Text = body.Note.Text };
pmcData.Notes.DataNotes.Add(newNote);
return _eventOutputHolder.GetOutput(sessionId);
}
@@ -41,10 +41,10 @@ public class NoteController(
NoteActionData body,
string sessionId)
{
Note noteToEdit = pmcData.Notes.DataNotes[body.Index!.Value];
var noteToEdit = pmcData.Notes.DataNotes[body.Index!.Value];
noteToEdit.Time = body.Note.Time;
noteToEdit.Text = body.Note.Text;
return _eventOutputHolder.GetOutput(sessionId);
}
@@ -11,7 +11,7 @@ namespace Core.Controllers;
public class NotifierController(
HttpServerHelper _httpServerHelper,
NotifierHelper _notifierHelper
)
)
{
/// <summary>
/// Resolve an array of session notifications.
@@ -59,9 +59,9 @@ public class NotifierController(
// _notificationService.UpdateMessageOnQueue(sessionID, []);
// resolve(messages);
//};
//};
// immediately check
// immediately check
// checkNotifications();
//});
}
@@ -31,6 +31,8 @@ public class PrestigeController(
ICloner _cloner
)
{
protected double _prestigePercentage = 0.05;
/// <summary>
/// Handle /client/prestige/list
/// </summary>
@@ -45,35 +47,39 @@ public class PrestigeController(
}
/// <summary>
/// Handle /client/prestige/obtain
/// <para>Handle /client/prestige/obtain</para>
/// Going to Prestige 1 grants the below
/// <list type="bullet">
/// <item>5% of skills should be transfered over</item>
/// <item>5% of mastering should be transfered over</item>
/// <item>Earned achievements should be transfered over</item>
/// <item>Profile stats should be transfered over</item>
/// <item>Prestige progress should be transfered over</item>
/// <item>Items and rewards for Prestige 1</item>
/// </list>
/// Going to Prestige 2 grants the below
/// <list type="bullet">
/// <item>10% of skills should be transfered over</item>
/// <item>10% of mastering should be transfered over</item>
/// <item>Earned achievements should be transfered over</item>
/// <item>Profile stats should be transfered over</item>
/// <item>Prestige progress should be transfered over</item>
/// <item>Items and rewards for Prestige 2</item>
/// </list>
/// Each time reseting the below
/// <list type="bullet">
/// <item>Trader standing</item>
/// <item>Task progress</item>
/// <item>Character level</item>
/// <item>Stash</item>
/// <item>Hideout progress</item>
/// </list>
/// </summary>
/// <param name="sessionId"></param>
/// <param name="request"></param>
/// <returns></returns>
public void ObtainPrestige(
string sessionId,
ObtainPrestigeRequestList request)
{
// Going to prestige 1
// transfer
// 5% of skills should be transfered over
// 5% of mastering should be transfered over
// earned achievements should be transfered over
// profile stats should be transfered over
// prestige progress should be transfered over
// reset
// trader standing
// task progress
// character level
// stash
// hideout progress
// going to prestige 2
// most likely the same, but wait for dump of new beginnings quest
// Clone existing profile, create a new one
var prePrestigeProfileClone = _cloner.Clone(_profileHelper.GetFullProfile(sessionId));
var prePrestigePmc = prePrestigeProfileClone.CharacterData.PmcData;
@@ -86,8 +92,7 @@ public class PrestigeController(
.Customization.FirstOrDefault(
(customisation) => customisation.Value.Name == prePrestigePmc.Info.Voice
)
.Value.Id,
SptForcePrestigeLevel = prePrestigeProfileClone.CharacterData.PmcData.Info.PrestigeLevel.GetValueOrDefault(0) + 1, // Current + 1
.Value.Id
};
// Reset profile
@@ -95,71 +100,61 @@ public class PrestigeController(
// Get freshly reset profile ready for editing
var newProfile = _profileHelper.GetFullProfile(sessionId);
// set this here so we can use the prestigeLevel for further calcs
newProfile.CharacterData.PmcData.Info.PrestigeLevel = prePrestigePmc.Info.PrestigeLevel ?? 0;
newProfile.CharacterData.PmcData.Info.PrestigeLevel++;
// Copy skills to new profile
var commonSkillsToCopy = prePrestigePmc.Skills.Common;
foreach (var skillToCopy in commonSkillsToCopy)
{
// Set progress 5% of what it was
skillToCopy.Progress = skillToCopy.Progress.Value * 0.05;
// Set progress 5% of what it was * prestige level to get 5% or 10% for prestige 1 or 2 respectivly
skillToCopy.Progress = (skillToCopy.Progress.Value * _prestigePercentage) * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
var existingSkill = newProfile.CharacterData.PmcData.Skills.Common.FirstOrDefault((skill) => skill.Id == skillToCopy.Id);
if (existingSkill is not null)
{
existingSkill.Progress = skillToCopy.Progress;
}
else
{
newProfile.CharacterData.PmcData.Skills.Common.Add(skillToCopy);
}
}
// Copy mastering to new profile
var masteringSkillsToCopy = prePrestigePmc.Skills.Mastering;
foreach (var skillToCopy in masteringSkillsToCopy)
{
// Set progress 5% of what it was
skillToCopy.Progress = skillToCopy.Progress.Value * 0.05;
// Set progress 5% of what it was * prestige level to get 5% or 10% for prestige 1 or 2 respectivly
skillToCopy.Progress = (skillToCopy.Progress.Value * _prestigePercentage) * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
var existingSkill = newProfile.CharacterData.PmcData.Skills.Mastering.FirstOrDefault(
(skill) => skill.Id == skillToCopy.Id
);
if (existingSkill is not null)
{
existingSkill.Progress = skillToCopy.Progress;
}
else
{
newProfile.CharacterData.PmcData.Skills.Mastering.Add(skillToCopy);
}
}
// Add existing completed achievements and new one for prestige
newProfile.CharacterData.PmcData.Achievements = prePrestigeProfileClone.CharacterData.PmcData.Achievements; // this *should* only contain completed ones
// Add "Prestigious" achievement
if (!newProfile.CharacterData.PmcData.Achievements.ContainsKey("676091c0f457869a94017a23"))
{
newProfile.CharacterData.PmcData.Achievements.Add("676091c0f457869a94017a23", _timeUtil.GetTimeStamp());
}
// TODO: is there one for second prestige
// Add existing Stats to profile
newProfile.CharacterData.PmcData.Stats = prePrestigePmc.Stats;
// Assumes Prestige data is in descending order
var indexOfPrestigeObtained = (int)Math.Min(createRequest.SptForcePrestigeLevel.Value - 1, 1); // Index starts at 0
var indexOfPrestigeObtained = newProfile.CharacterData.PmcData.Info.PrestigeLevel ?? 0; // Index starts at 0
var currentPrestigeData = _databaseService.GetTemplates().Prestige.Elements[indexOfPrestigeObtained];
var prestigeRewards = _databaseService.GetTemplates()
.Prestige.Elements.Slice(0, indexOfPrestigeObtained + 1)
.SelectMany((prestige) => prestige.Rewards);
var prestigeRewards = currentPrestigeData.Rewards;
AddPrestigeRewardsToProfile(sessionId, newProfile, prestigeRewards);
// Flag profile as having achieved this prestige level
newProfile.CharacterData.PmcData.Prestige[currentPrestigeData.Id] = _timeUtil.GetTimeStamp();
newProfile.CharacterData.PmcData.Info.PrestigeLevel++;
if (request is not null)
{
// Copy transferred items
foreach (var transferRequest in request)
{
@@ -169,7 +164,7 @@ public class PrestigeController(
ItemWithModsToAdd = [item],
FoundInRaid = item.Upd?.SpawnedInSession,
UseSortingTable = false,
Callback = null,
Callback = null
};
_inventoryHelper.AddItemToStash(
sessionId,
@@ -178,40 +173,39 @@ public class PrestigeController(
_eventOutputHolder.GetOutput(sessionId)
);
}
}
// Force save of above changes to disk
_saveServer.SaveProfile(sessionId);
}
private void AddPrestigeRewardsToProfile(string sessionId, SptProfile newProfile, IEnumerable<Reward> rewards)
{
foreach (var reward in rewards) {
switch (reward.Type) {
case RewardType.CustomizationDirect: {
foreach (var reward in rewards)
switch (reward.Type)
{
case RewardType.CustomizationDirect:
{
_profileHelper.AddHideoutCustomisationUnlock(newProfile, reward, CustomisationSource.PRESTIGE);
break;
}
case RewardType.Skill:
if (Enum.TryParse(reward.Target, out SkillTypes result))
{
_profileHelper.AddSkillPointsToPlayer(
newProfile.CharacterData.PmcData,
result,
((JsonElement)reward.Value).ToObject<double>()
);
}
else
{
_logger.Error($"Unable to parse reward Target to Enum: {reward.Target}");
}
break;
case RewardType.Item: {
AddItemDirectRequest addItemRequest = new AddItemDirectRequest {
case RewardType.Item:
{
var addItemRequest = new AddItemDirectRequest
{
ItemWithModsToAdd = reward.Items,
FoundInRaid = reward.Items.FirstOrDefault()?.Upd?.SpawnedInSession,
UseSortingTable = false,
Callback = null,
Callback = null
};
_inventoryHelper.AddItemToStash(
sessionId,
@@ -221,14 +215,14 @@ public class PrestigeController(
);
break;
}
// case "ExtraDailyQuest": {
// // todo
// break;
// }
case RewardType.ExtraDailyQuest:
{
_logger.Info("additional quests will be added when generating repeatables");
break;
}
default:
_logger.Error($"Unhandled prestige reward type: {reward.Type}");
break;
}
}
}
}
+20 -43
View File
@@ -51,19 +51,15 @@ public class ProfileController(
public MiniProfile GetMiniProfile(string sessionID)
{
var profile = _saveServer.GetProfile(sessionID);
if (profile?.CharacterData == null)
{
throw new Exception($"Unable to find character data for id: {sessionID}. Profile may be corrupt");
}
if (profile?.CharacterData == null) throw new Exception($"Unable to find character data for id: {sessionID}. Profile may be corrupt");
var pmc = profile.CharacterData.PmcData;
var maxLvl = _profileHelper.GetMaxLevel();
// Player hasn't completed profile creation process, send defaults
var currlvl = pmc?.Info?.Level.GetValueOrDefault(1);
var xpToNextLevel = _profileHelper.GetExperience(((currlvl ?? 1) + 1));
var xpToNextLevel = _profileHelper.GetExperience((currlvl ?? 1) + 1);
if (pmc?.Info?.Level == null)
{
return new MiniProfile
{
Username = profile.ProfileInfo?.Username ?? "",
@@ -77,9 +73,8 @@ public class ProfileController(
MaxLevel = maxLvl,
Edition = profile.ProfileInfo?.Edition ?? "",
ProfileId = profile.ProfileInfo?.ProfileId ?? "",
SptData = _profileHelper.GetDefaultSptDataObject(),
SptData = _profileHelper.GetDefaultSptDataObject()
};
}
return new MiniProfile
{
@@ -87,14 +82,14 @@ public class ProfileController(
Nickname = pmc.Info.Nickname,
HasPassword = profile.ProfileInfo.Password != "",
Side = pmc.Info.Side,
CurrentLevel = (int)(pmc.Info.Level),
CurrentExperience = (pmc.Info.Experience ?? 0),
CurrentLevel = (int)pmc.Info.Level,
CurrentExperience = pmc.Info.Experience ?? 0,
PreviousExperience = currlvl == 0 ? 0 : _profileHelper.GetExperience((int)currlvl),
NextLevel = xpToNextLevel,
MaxLevel = maxLvl,
Edition = profile.ProfileInfo?.Edition ?? "",
ProfileId = profile.ProfileInfo?.ProfileId ?? "",
SptData = profile.SptData,
SptData = profile.SptData
};
}
@@ -133,15 +128,9 @@ public class ProfileController(
*/
public string ValidateNickname(ValidateNicknameRequestData info, string sessionID)
{
if (info.Nickname.Length < 3)
{
return "tooshort";
}
if (info.Nickname.Length < 3) return "tooshort";
if (_profileHelper.IsNicknameTaken(info, sessionID))
{
return "taken";
}
if (_profileHelper.IsNicknameTaken(info, sessionID)) return "taken";
return "OK";
}
@@ -188,10 +177,7 @@ public class ProfileController(
{
var pmcProfile = profile?.CharacterData?.PmcData;
if (!pmcProfile?.Info?.LowerNickname?.Contains(info.Nickname.ToLower()) ?? false)
{
continue;
}
if (!pmcProfile?.Info?.LowerNickname?.Contains(info.Nickname.ToLower()) ?? false) continue;
result.Add(_profileHelper.GetChatRoomMemberFromPmcProfile(pmcProfile));
}
@@ -210,8 +196,8 @@ public class ProfileController(
MaxPveCountExceeded = false,
Profiles =
[
new() { ProfileId = account.ScavengerId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 },
new() { ProfileId = account.ProfileId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 },
new ProfileStatusData { ProfileId = account.ScavengerId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 },
new ProfileStatusData { ProfileId = account.ProfileId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 }
]
};
@@ -261,7 +247,7 @@ public class ProfileController(
MemberCategory = profileToViewPmc.Info.MemberCategory as int?,
BannedState = profileToViewPmc.Info.BannedState,
BannedUntil = profileToViewPmc.Info.BannedUntil,
RegistrationDate = profileToViewPmc.Info.RegistrationDate,
RegistrationDate = profileToViewPmc.Info.RegistrationDate
},
Customization =
{
@@ -269,13 +255,13 @@ public class ProfileController(
Body = profileToViewPmc.Customization.Body,
Feet = profileToViewPmc.Customization.Feet,
Hands = profileToViewPmc.Customization.Hands,
Dogtag = profileToViewPmc.Customization.DogTag,
Dogtag = profileToViewPmc.Customization.DogTag
},
Skills = profileToViewPmc.Skills,
Equipment =
{
Id = profileToViewPmc.Inventory.Equipment,
Items = profileToViewPmc.Inventory.Items,
Items = profileToViewPmc.Inventory.Items
},
Achievements = profileToViewPmc.Achievements,
FavoriteItems = _profileHelper.GetOtherProfileFavorites(profileToViewPmc),
@@ -284,15 +270,15 @@ public class ProfileController(
Eft =
{
TotalInGameTime = profileToViewPmc.Stats.Eft.TotalInGameTime,
OverAllCounters = profileToViewPmc.Stats.Eft.OverallCounters,
},
OverAllCounters = profileToViewPmc.Stats.Eft.OverallCounters
}
},
ScavStats =
{
Eft =
{
TotalInGameTime = profileToViewScav.Stats.Eft.TotalInGameTime,
OverAllCounters = profileToViewScav.Stats.Eft.OverallCounters,
OverAllCounters = profileToViewScav.Stats.Eft.OverallCounters
}
},
Hideout = profileToViewPmc.Hideout,
@@ -308,20 +294,11 @@ public class ProfileController(
public bool SetChosenProfileIcon(string sessionId, GetProfileSettingsRequest request)
{
var profileToUpdate = _profileHelper.GetPmcProfile(sessionId);
if (profileToUpdate == null)
{
return false;
}
if (profileToUpdate == null) return false;
if (request.MemberCategory != null)
{
profileToUpdate.Info.SelectedMemberCategory = request.MemberCategory as MemberCategory?;
}
if (request.MemberCategory != null) profileToUpdate.Info.SelectedMemberCategory = request.MemberCategory as MemberCategory?;
if (request.SquadInviteRestriction != null)
{
profileToUpdate.Info.SquadInviteRestriction = request.SquadInviteRestriction;
}
if (request.SquadInviteRestriction != null) profileToUpdate.Info.SquadInviteRestriction = request.SquadInviteRestriction;
return true;
}
+10 -33
View File
@@ -107,10 +107,7 @@ public class QuestController(
acceptedQuest.QuestId,
sessionID
);
if (newlyAccessibleQuests.Count > 0)
{
acceptQuestResponse.ProfileChanges[sessionID].Quests.AddRange(newlyAccessibleQuests);
}
if (newlyAccessibleQuests.Count > 0) acceptQuestResponse.ProfileChanges[sessionID].Quests.AddRange(newlyAccessibleQuests);
return acceptQuestResponse;
}
@@ -120,11 +117,9 @@ public class QuestController(
foreach (var condition in questConditions)
{
if (pmcData.TaskConditionCounters.TryGetValue(condition.Id, out var counter))
{
_logger.Error(
$"Unable to add new task condition counter: {condition.ConditionType} for qeust: {questId} to profile: {pmcData.SessionId} as it already exists:"
);
}
switch (condition.ConditionType)
{
@@ -134,7 +129,7 @@ public class QuestController(
Id = condition.Id,
SourceId = questId,
Type = condition.ConditionType,
Value = 0,
Value = 0
};
break;
}
@@ -186,10 +181,7 @@ public class QuestController(
var matchingQuest = repeatableQuest.ActiveQuests.FirstOrDefault(x => x.Id == acceptedQuest.QuestId);
if (matchingQuest is not null)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Accepted repeatable quest {acceptedQuest.QuestId} from {repeatableQuest.Name}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Accepted repeatable quest {acceptedQuest.QuestId} from {repeatableQuest.Name}");
matchingQuest.SptRepatableGroupName = repeatableQuest.Name;
return matchingQuest;
@@ -217,7 +209,6 @@ public class QuestController(
// Decrement number of items handed in
QuestCondition? handoverRequirements = null;
foreach (var condition in quest.Conditions.AvailableForFinish)
{
if (condition.Id == handoverQuestRequest.ConditionId && handoverQuestTypes.Contains(condition.ConditionType))
{
handedInCount = int.Parse(condition.Value.ToString());
@@ -249,19 +240,14 @@ public class QuestController(
break;
}
}
}
if (isItemHandoverQuest && handedInCount == 0)
{
return ShowRepeatableQuestInvalidConditionError(handoverQuestRequest, output);
}
if (isItemHandoverQuest && handedInCount == 0) return ShowRepeatableQuestInvalidConditionError(handoverQuestRequest, output);
var totalItemCountToRemove = 0d;
foreach (var itemHandover in handoverQuestRequest.Items)
{
var matchingItemInProfile = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemHandover.Id);
if (!(matchingItemInProfile is not null && handoverRequirements.Target.List.Contains(matchingItemInProfile.Template)))
{
// Item handed in by player doesnt match what was requested
return ShowQuestItemHandoverMatchError(
handoverQuestRequest,
@@ -269,7 +255,6 @@ public class QuestController(
handoverRequirements,
output
);
}
// Remove the right quantity of given items
var itemCountToRemove = Math.Min(itemHandover.Count ?? 0, handedInCount - totalItemCountToRemove);
@@ -284,10 +269,7 @@ public class QuestController(
sessionID,
output
);
if (totalItemCountToRemove == handedInCount)
{
break;
}
if (totalItemCountToRemove == handedInCount) break;
}
else
{
@@ -306,13 +288,12 @@ public class QuestController(
// Important: loop backward when removing items from the array we're looping on
while (index-- > 0)
{
if (toRemove.Contains(pmcData.Inventory.Items[index].Id))
{
var removedItem = _cloner.Clone(pmcData.Inventory.Items[index]);
pmcData.Inventory.Items.RemoveAt(index);
// Remove the item
// Remove the item
// If the removed item has a numeric `location` property, re-calculate all the child
// element `location` properties of the parent so they are sequential, while retaining order
if (removedItem.Location.GetType() == typeof(int))
@@ -324,15 +305,11 @@ public class QuestController(
childItems.RemoveAt(0); // Remove the parent
// Sort by the current `location` and update
childItems.Sort((a, b) => (((int)a.Location) > ((int)b.Location) ? 1 : -1));
childItems.Sort((a, b) => (int)a.Location > (int)b.Location ? 1 : -1);
for (int i = 0; i < childItems.Count; i++)
{
childItems[i].Location = i;
}
for (var i = 0; i < childItems.Count; i++) childItems[i].Location = i;
}
}
}
}
}
@@ -370,7 +347,7 @@ public class QuestController(
{
questId = handoverQuestRequest.QuestId,
handedInTpl = itemHandedOver?.Template ?? "UNKNOWN",
requiredTpl = handoverRequirements.Target.List.FirstOrDefault(),
requiredTpl = handoverRequirements.Target.List.FirstOrDefault()
}
);
_logger.Error(errorMessage);
@@ -392,7 +369,7 @@ public class QuestController(
Id = conditionId,
SourceId = questId,
Type = "HandoverItem",
Value = counterValue,
Value = counterValue
};
}
+48 -137
View File
@@ -119,9 +119,7 @@ public class RagfairController
if (
pmcProfile.RagfairInfo is not null && pmcProfile.Info.Level >= _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel
)
{
_ragfairOfferHelper.ProcessOffersOnProfile(sessionId);
}
}
}
@@ -142,16 +140,14 @@ public class RagfairController
{
Offers = [],
OffersCount = searchRequest.Limit,
SelectedCategory = searchRequest.HandbookId,
SelectedCategory = searchRequest.HandbookId
};
result.Offers = GetOffersForSearchType(searchRequest, itemsToAdd, traderAssorts, profile.CharacterData.PmcData);
// Client requested a category refresh
if (searchRequest.UpdateOfferCount.GetValueOrDefault(false))
{
result.Categories = GetSpecificCategories(profile.CharacterData.PmcData, searchRequest, result.Offers);
}
AddIndexValueToOffers(result.Offers);
@@ -167,10 +163,7 @@ public class RagfairController
{
// For the items, check the barter schemes. The method getDisplayableAssorts sets a flag sptQuestLocked
// to true if the quest is not completed yet
if (_ragfairOfferHelper.TraderOfferItemQuestLocked(traderOffer, traderAssorts))
{
traderOffer.Locked = true;
}
if (_ragfairOfferHelper.TraderOfferItemQuestLocked(traderOffer, traderAssorts)) traderOffer.Locked = true;
// Update offers BuyRestrictionCurrent/BuyRestrictionMax values
SetTraderOfferPurchaseLimits(traderOffer, profile);
@@ -208,7 +201,7 @@ public class RagfairController
new
{
offerId = offer.Items.First().Id,
traderId = offer.User.Id,
traderId = offer.User.Id
}
)
);
@@ -263,10 +256,7 @@ public class RagfairController
{
var counter = 0;
foreach (var offer in offers)
{
offer.InternalId = ++counter;
}
foreach (var offer in offers) offer.InternalId = ++counter;
}
/**
@@ -293,10 +283,7 @@ public class RagfairController
else
{
_logger.Error(_localisationService.GetText("ragfair-unable_to_get_categories"));
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(_jsonUtil.Serialize(searchRequest));
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug(_jsonUtil.Serialize(searchRequest));
return new Dictionary<string, int>();
}
@@ -335,15 +322,9 @@ public class RagfairController
PmcData pmcProfile)
{
// Searching for items in preset menu
if (searchRequest.BuildCount > 0)
{
return _ragfairOfferHelper.GetOffersForBuild(searchRequest, itemsToAdd, traderAssorts, pmcProfile);
}
if (searchRequest.BuildCount > 0) return _ragfairOfferHelper.GetOffersForBuild(searchRequest, itemsToAdd, traderAssorts, pmcProfile);
if (searchRequest.NeededSearchId?.Length > 0)
{
return _ragfairOfferHelper.GetOffersThatRequireItem(searchRequest, pmcProfile);
}
if (searchRequest.NeededSearchId?.Length > 0) return _ragfairOfferHelper.GetOffersThatRequireItem(searchRequest, pmcProfile);
// Searching for general items
return _ragfairOfferHelper.GetValidOffers(searchRequest, itemsToAdd, traderAssorts, pmcProfile);
@@ -375,10 +356,7 @@ public class RagfairController
// No offers listed, get price from live ragfair price list prices.json
// No flea price, get handbook price
var fleaPrices = _databaseService.GetPrices();
if (!fleaPrices.TryGetValue(getPriceRequest.TemplateId, out var tplPrice))
{
tplPrice = _handbookHelper.GetTemplatePrice(getPriceRequest.TemplateId);
}
if (!fleaPrices.TryGetValue(getPriceRequest.TemplateId, out var tplPrice)) tplPrice = _handbookHelper.GetTemplatePrice(getPriceRequest.TemplateId);
return new GetItemPriceResult { Avg = tplPrice, Min = tplPrice, Max = tplPrice };
}
@@ -391,40 +369,26 @@ public class RagfairController
foreach (var offer in offers)
{
// Exclude barter items, they tend to have outrageous equivalent prices
if (offer.Requirements.Any(req => !_paymentHelper.IsMoneyTpl(req.Template)))
{
continue;
}
if (offer.Requirements.Any(req => !_paymentHelper.IsMoneyTpl(req.Template))) continue;
if (ignoreTraderOffers && _ragfairOfferHelper.OfferIsFromTrader(offer))
{
continue;
}
if (ignoreTraderOffers && _ragfairOfferHelper.OfferIsFromTrader(offer)) continue;
// Figure out how many items the requirementsCost is applying to, and what the per-item price is
var offerItemCount = offer.SellInOnePiece.GetValueOrDefault(false)
? (offer.Items.First().Upd?.StackObjectsCount ?? 1)
? offer.Items.First().Upd?.StackObjectsCount ?? 1
: 1;
var perItemPrice = offer.RequirementsCost / offerItemCount;
// Handle min/max calculations based on the per-item price
if (perItemPrice < minMax.Min)
{
minMax.Min = perItemPrice;
}
else if (perItemPrice > minMax.Max)
{
minMax.Max = perItemPrice;
}
else if (perItemPrice > minMax.Max) minMax.Max = perItemPrice;
sum += perItemPrice.Value;
totalOfferCount++;
}
if (totalOfferCount == 0)
{
return -1d;
}
if (totalOfferCount == 0) return -1d;
return sum / totalOfferCount;
}
@@ -442,16 +406,11 @@ public class RagfairController
var fullProfile = _profileHelper.GetFullProfile(sessionID);
var validationMessage = "";
if (!IsValidPlayerOfferRequest(offerRequest, validationMessage))
{
return _httpResponseUtil.AppendErrorToOutput(output, validationMessage);
}
if (!IsValidPlayerOfferRequest(offerRequest, validationMessage)) return _httpResponseUtil.AppendErrorToOutput(output, validationMessage);
var typeOfOffer = GetOfferType(offerRequest);
if (typeOfOffer == FleaOfferType.UNKNOWN)
{
return _httpResponseUtil.AppendErrorToOutput(output, $"Unknown offer type: {typeOfOffer}, cannot list item on flea");
}
switch (typeOfOffer)
{
@@ -504,21 +463,12 @@ public class RagfairController
if (!sellInOncePiece)
{
if (offerRequest.Items.Count == 1)
{
return FleaOfferType.SINGLE;
}
if (offerRequest.Items.Count == 1) return FleaOfferType.SINGLE;
if (offerRequest.Items.Count > 1)
{
return FleaOfferType.MULTI;
}
if (offerRequest.Items.Count > 1) return FleaOfferType.MULTI;
}
if (sellInOncePiece)
{
return FleaOfferType.PACK;
}
if (sellInOncePiece) return FleaOfferType.PACK;
return FleaOfferType.UNKNOWN;
}
@@ -542,14 +492,12 @@ public class RagfairController
// Get first item and its children and use as template
var firstListingAndChidren = _itemHelper.FindAndReturnChildrenAsItems(
pmcData.Inventory.Items,
offerRequest.Items[0]);
offerRequest.Items[0]
);
// Find items to be listed on flea (+ children) from player inventory
var result = GetItemsToListOnFleaFromInventory(pmcData, offerRequest.Items);
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage))
{
_httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
}
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage)) _httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
// Total count of items summed using their stack counts
var stackCountTotal = _ragfairOfferHelper.GetTotalStackCountSize(result.Items);
@@ -557,7 +505,7 @@ public class RagfairController
// When listing identical items on flea, condense separate items into one stack with a merged stack count
// e.g. 2 ammo items, stackObjectCount = 3 for each, will result in 1 stack of 6
firstListingAndChidren[0].Upd ??= new Upd{ };
firstListingAndChidren[0].Upd ??= new Upd { };
firstListingAndChidren[0].Upd.StackObjectsCount = stackCountTotal;
@@ -568,14 +516,11 @@ public class RagfairController
var newRootOfferItem = offer.Items[0];
// Average offer price for single item (or whole weapon)
var averages = GetItemMinAvgMaxFleaPriceValues(new GetMarketPriceRequestData{ TemplateId = offer.Items[0].Template });
var averages = GetItemMinAvgMaxFleaPriceValues(new GetMarketPriceRequestData { TemplateId = offer.Items[0].Template });
var averageOfferPrice = averages.Avg;
// Check for and apply item price modifer if it exists in config
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(newRootOfferItem.Template, out var itemPriceModifer))
{
averageOfferPrice *= itemPriceModifer;
}
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(newRootOfferItem.Template, out var itemPriceModifer)) averageOfferPrice *= itemPriceModifer;
// Get average of item+children quality
var qualityMultiplier = _itemHelper.GetItemQualityModifierForItems(offer.Items, true);
@@ -590,7 +535,8 @@ public class RagfairController
var sellChancePercent = _ragfairSellHelper.CalculateSellChance(
averageOfferPrice.Value,
playerListedPriceInRub,
qualityMultiplier);
qualityMultiplier
);
// Create array of sell times for items listed
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal);
@@ -605,11 +551,9 @@ public class RagfairController
playerListedPriceInRub,
(int)stackCountTotal,
offerRequest,
output);
if (taxFeeChargeFailed)
{
return output;
}
output
);
if (taxFeeChargeFailed) return output;
}
// Add offer to players profile + add to client response
@@ -617,9 +561,7 @@ public class RagfairController
output.ProfileChanges[sessionID].RagFairOffers.Add(offer);
// Remove items from inventory after creating offer
foreach (var itemToRemove in offerRequest.Items) {
_inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
}
foreach (var itemToRemove in offerRequest.Items) _inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
return output;
}
@@ -643,14 +585,12 @@ public class RagfairController
// Get first item and its children and use as template
var firstListingAndChidren = _itemHelper.FindAndReturnChildrenAsItems(
pmcData.Inventory.Items,
offerRequest.Items[0]);
offerRequest.Items[0]
);
// Find items to be listed on flea (+ children) from player inventory
var result = GetItemsToListOnFleaFromInventory(pmcData, offerRequest.Items);
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage))
{
_httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
}
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage)) _httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
// Total count of items summed using their stack counts
var stackCountTotal = _ragfairOfferHelper.GetTotalStackCountSize(result.Items);
@@ -668,14 +608,11 @@ public class RagfairController
var newRootOfferItem = offer.Items[0];
// Single price for an item
var averages = GetItemMinAvgMaxFleaPriceValues( new GetMarketPriceRequestData{ TemplateId = firstListingAndChidren[0].Template });
var averages = GetItemMinAvgMaxFleaPriceValues(new GetMarketPriceRequestData { TemplateId = firstListingAndChidren[0].Template });
var singleItemPrice = averages.Avg;
// Check for and apply item price modifer if it exists in config
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(newRootOfferItem.Template, out double itemPriceModifer))
{
singleItemPrice *= itemPriceModifer;
}
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(newRootOfferItem.Template, out var itemPriceModifer)) singleItemPrice *= itemPriceModifer;
// Get average of item+children quality
var qualityMultiplier = _itemHelper.GetItemQualityModifierForItems(offer.Items, true);
@@ -690,7 +627,8 @@ public class RagfairController
var sellChancePercent = _ragfairSellHelper.CalculateSellChance(
singleItemPrice.Value * stackCountTotal,
playerListedPriceInRub,
qualityMultiplier);
qualityMultiplier
);
// Create array of sell times for items listed + sell all at once as its a pack
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal, true);
@@ -705,11 +643,9 @@ public class RagfairController
playerListedPriceInRub,
(int)stackCountTotal,
offerRequest,
output);
if (taxFeeChargeFailed)
{
return output;
}
output
);
if (taxFeeChargeFailed) return output;
}
// Add offer to players profile + add to client response
@@ -717,9 +653,7 @@ public class RagfairController
output.ProfileChanges[sessionID].RagFairOffers.Add(offer);
// Remove items from inventory after creating offer
foreach (var itemToRemove in offerRequest.Items) {
_inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
}
foreach (var itemToRemove in offerRequest.Items) _inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
return output;
}
@@ -741,10 +675,7 @@ public class RagfairController
// Find items to be listed on flea from player inventory
var result = GetItemsToListOnFleaFromInventory(pmcData, offerRequest.Items);
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage))
{
_httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
}
if (result.Items is null || !string.IsNullOrEmpty(result.ErrorMessage)) _httpResponseUtil.AppendErrorToOutput(output, result.ErrorMessage);
// Total count of items summed using their stack counts
var stackCountTotal = _ragfairOfferHelper.GetTotalStackCountSize(result.Items);
@@ -767,10 +698,8 @@ public class RagfairController
var averageOfferPriceSingleItem = averages.Avg;
// Check for and apply item price modifer if it exists in config
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(rootItem.Template, out double itemPriceModifer))
{
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(rootItem.Template, out var itemPriceModifer))
averageOfferPriceSingleItem *= itemPriceModifer;
}
// Multiply single item price by quality
averageOfferPriceSingleItem *= qualityMultiplier;
@@ -795,10 +724,7 @@ public class RagfairController
offerRequest,
output
);
if (taxFeeChargeFailed)
{
return output;
}
if (taxFeeChargeFailed) return output;
}
// Add offer to players profile + add to client response
@@ -806,10 +732,7 @@ public class RagfairController
output.ProfileChanges[sessionID].RagFairOffers.Add(offer);
// Remove items from inventory after creating offer
foreach (var itemToRemove in offerRequest.Items)
{
_inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
}
foreach (var itemToRemove in offerRequest.Items) _inventoryHelper.RemoveItem(pmcData, itemToRemove, sessionID, output);
return output;
}
@@ -846,10 +769,7 @@ public class RagfairController
offerRequest.SellInOnePiece.GetValueOrDefault(false)
);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Offer tax to charge: {tax}, pulled from client: {storedClientTaxValue.Count is not null}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Offer tax to charge: {tax}, pulled from client: {storedClientTaxValue.Count is not null}");
// cleanup of cache now we've used the tax value from it
_ragfairTaxService.ClearStoredOfferTaxById(offerRequest.Items.First());
@@ -892,7 +812,7 @@ public class RagfairController
{
Template = item.Template,
Count = item.Count,
OnlyFunctional = item.OnlyFunctional,
OnlyFunctional = item.OnlyFunctional
}
);
@@ -919,13 +839,9 @@ public class RagfairController
var requestedItemTpl = item.Template;
if (_paymentHelper.IsMoneyTpl(requestedItemTpl))
{
requirementsPriceInRub += _handbookHelper.InRUB(item.Count.Value, requestedItemTpl);
}
else
{
requirementsPriceInRub += _itemHelper.GetDynamicItemPrice(requestedItemTpl).Value * item.Count.Value;
}
}
return requirementsPriceInRub;
@@ -1050,10 +966,7 @@ public class RagfairController
{
var count = 1;
var sellInOncePiece = playerOffer.SellInOnePiece.GetValueOrDefault(false);
if (!sellInOncePiece)
{
count = (int)playerOffer.Items.Sum(offerItem => offerItem.Upd?.StackObjectsCount ?? 0);
}
if (!sellInOncePiece) count = (int)playerOffer.Items.Sum(offerItem => offerItem.Upd?.StackObjectsCount ?? 0);
var tax = _ragfairTaxService.CalculateTax(
playerOffer.Items.First(),
@@ -1066,12 +979,10 @@ public class RagfairController
var request = CreateBuyTradeRequestObject(CurrencyType.RUB, tax);
_paymentService.PayMoney(pmcData, request, sessionId, output);
if (output.Warnings.Count > 0)
{
return _httpResponseUtil.AppendErrorToOutput(
output,
_localisationService.GetText("ragfair-unable_to_pay_commission_fee")
);
}
}
// Add extra time to offer
@@ -1096,7 +1007,7 @@ public class RagfairController
Type = "",
ItemId = "",
Count = 0,
SchemeId = 0,
SchemeId = 0
};
}
@@ -11,7 +11,7 @@ namespace Core.Controllers;
public class RepairController(
EventOutputHolder _eventOutputHolder,
RepairService _repairService
)
)
{
/// <summary>
/// Handle TraderRepair event
@@ -29,7 +29,8 @@ public class RepairController(
var output = _eventOutputHolder.GetOutput(sessionID);
// find the item to repair
foreach (var repairItem in body.RepairItems) {
foreach (var repairItem in body.RepairItems)
{
var repairDetails = _repairService.RepairItemByTrader(sessionID, pmcData, repairItem, body.TId);
_repairService.PayForRepair(
@@ -38,12 +39,10 @@ public class RepairController(
repairItem.Id,
repairDetails.RepairCost.Value,
body.TId,
output);
output
);
if (output.Warnings?.Count > 0)
{
return output;
}
if (output.Warnings?.Count > 0) return output;
// Add repaired item to output object
output.ProfileChanges[sessionID].Items.ChangedItems.Add(repairDetails.RepairedItem);
@@ -89,10 +89,7 @@ public class RepeatableQuestController(
);
// If the configuration dictates to replace with the same quest type, adjust the available quest types
if (repeatableConfig?.KeepDailyQuestTypeOnReplacement is not null)
{
repeatableConfig.Types = [questToReplace.Type.ToString()];
}
if (repeatableConfig?.KeepDailyQuestTypeOnReplacement is not null) repeatableConfig.Types = [questToReplace.Type.ToString()];
// Generate meta-data for what type/levelrange of quests can be generated for player
var allowedQuestTypes = GenerateQuestPool(repeatableConfig, pmcData.Info.Level);
@@ -117,11 +114,9 @@ public class RepeatableQuestController(
repeatablesOfTypeInProfile.ActiveQuests.Add(newRepeatableQuest);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Removing: {repeatableConfig.Name} quest: {questToReplace.Id} from trader: {questToReplace.TraderId} as its been replaced"
);
}
RemoveQuestFromProfile(fullProfile, questToReplace.Id);
@@ -153,10 +148,7 @@ public class RepeatableQuestController(
// Not free, Charge player + appy charisma bonus to cost of replacement
cost.Count = (int)Math.Truncate(cost.Count.Value * (1 - Math.Truncate(charismaBonus / 100) * 0.001));
_paymentService.AddPaymentToOutput(pmcData, cost.TemplateId, cost.Count.Value, sessionID, output);
if (output.Warnings.Count > 0)
{
return output;
}
if (output.Warnings.Count > 0) return output;
}
}
@@ -227,16 +219,12 @@ public class RepeatableQuestController(
string replacedQuestId)
{
if (repeatablesOfTypeInProfile.ActiveQuests.Count == 1)
{
// Only one repeatable quest being replaced (e.g. scav_daily), remove everything ready for new quest requirement to be added
// Will assist in cleanup of existing profiles data
repeatablesOfTypeInProfile.ChangeRequirement.Clear();
}
else
{
// Multiple active quests of this type (e.g. daily or weekly) are active, just remove the single replaced quest
repeatablesOfTypeInProfile.ChangeRequirement.Remove(replacedQuestId);
}
}
private RepeatableQuest? AttemptToGenerateRepeatableQuest(string sessionId, PmcData pmcData,
@@ -256,18 +244,13 @@ public class RepeatableQuestController(
);
if (newRepeatableQuest is not null)
{
// Successfully generated a quest, exit loop
break;
}
attempts++;
}
if (attempts > maxAttempts)
{
_logger.Error("We were stuck in repeatable quest generation. This should never happen. Please report");
}
if (attempts > maxAttempts) _logger.Error("We were stuck in repeatable quest generation. This should never happen. Please report");
return newRepeatableQuest;
}
@@ -279,12 +262,10 @@ public class RepeatableQuestController(
// Find quest we're replacing in scav profile quests array and remove it
if (fullProfile.CharacterData.ScavData is not null)
{
_questHelper.FindAndRemoveQuestFromArrayIfExists(
questToReplaceId,
fullProfile.CharacterData.ScavData.Quests
);
}
}
/**
@@ -301,10 +282,8 @@ public class RepeatableQuestController(
var questToReplace =
repeatablesInProfile.ActiveQuests.FirstOrDefault(repeatable => repeatable.Id == questId);
if (questToReplace is null)
{
// Not found, skip to next repeatable sub-type
continue;
}
return new GetRepeatableByIdResult { Quest = questToReplace, RepeatableType = repeatablesInProfile };
}
@@ -328,20 +307,15 @@ public class RepeatableQuestController(
var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData);
if (!canAccessRepeatables)
{
// Don't send any repeatables, even existing ones
continue;
}
// Existing repeatables are still valid, add to return data and move to next sub-type
if (currentTime < generatedRepeatables.EndTime - 1)
{
returnData.Add(generatedRepeatables);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"[Quest Check] {repeatableTypeLower} quests are still valid.");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"[Quest Check] {repeatableTypeLower} quests are still valid.");
continue;
}
@@ -351,10 +325,7 @@ public class RepeatableQuestController(
// Set endtime to be now + new duration
generatedRepeatables.EndTime = currentTime + repeatableConfig.ResetTime;
generatedRepeatables.InactiveQuests = [];
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Generating new {repeatableTypeLower}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generating new {repeatableTypeLower}");
// Put old quests to inactive (this is required since only then the client makes them fail due to non-completion)
// Also need to push them to the "inactiveQuests" list since we need to remove them from offraidData.profile.Quests
@@ -391,10 +362,7 @@ public class RepeatableQuestController(
}
// check if there are no more quest types available
if (questTypePool.Types.Count == 0)
{
break;
}
if (questTypePool.Types.Count == 0) break;
quest.Side = repeatableConfig.Side;
generatedRepeatables.ActiveQuests.Add(quest);
@@ -402,8 +370,7 @@ public class RepeatableQuestController(
// Nullguard
fullProfile.SptData.FreeRepeatableRefreshUsedCount ??= new Dictionary<string, int>();
// Reset players free quest count for this repeatable sub-type as we're generating new repeatables for this group (daily/weekly)
fullProfile.SptData.FreeRepeatableRefreshUsedCount[repeatableTypeLower] = 0;
@@ -473,18 +440,12 @@ public class RepeatableQuestController(
private bool CanProfileAccessRepeatableQuests(RepeatableQuestConfig repeatableConfig, PmcData pmcData)
{
// PMC and daily quests not unlocked yet
if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig))
{
return false;
}
if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)) return false;
// Scav and daily quests not unlocked yet
if (repeatableConfig.Side == "Scav" && !PlayerHasDailyScavQuestsUnlocked(pmcData))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug("Daily scav quests still locked, Intel center not built");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug("Daily scav quests still locked, Intel center not built");
return false;
}
@@ -521,21 +482,16 @@ public class RepeatableQuestController(
foreach (var activeQuest in generatedRepeatables.ActiveQuests)
{
var questStatusInProfile = pmcData.Quests.FirstOrDefault(quest => quest.QId == activeQuest.Id);
if (questStatusInProfile is null)
{
continue;
}
if (questStatusInProfile is null) continue;
// Keep finished quests in list so player can hand in
if (questStatusInProfile.Status == QuestStatusEnum.AvailableForFinish)
{
questsToKeep.Add(activeQuest);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug( // TODO: this shouldnt happen, doesnt on live
$"Keeping repeatable quest: {activeQuest.Id} in activeQuests since it is available to hand in"
);
}
continue;
}
@@ -632,17 +588,10 @@ public class RepeatableQuestController(
{
var locationNames = new List<string>();
foreach (var locationName in value)
{
if (IsPmcLevelAllowedOnLocation(locationName, pmcLevel))
{
locationNames.Add(locationName);
}
}
if (locationNames.Count > 0)
{
allowedLocation[location] = locationNames;
}
if (locationNames.Count > 0) allowedLocation[location] = locationNames;
}
return allowedLocation;
@@ -657,16 +606,10 @@ public class RepeatableQuestController(
protected bool IsPmcLevelAllowedOnLocation(string location, int pmcLevel)
{
// All PMC levels are allowed for 'any' location requirement
if (location == ELocationName.any.ToString())
{
return true;
}
if (location == ELocationName.any.ToString()) return true;
var locationBase = _databaseService.GetLocation(location.ToLower())?.Base;
if (locationBase is null)
{
return true;
}
if (locationBase is null) return true;
return pmcLevel <= locationBase.RequiredPlayerLevelMax && pmcLevel >= locationBase.RequiredPlayerLevelMin;
}
@@ -680,14 +623,10 @@ public class RepeatableQuestController(
private int GetQuestCount(RepeatableQuestConfig repeatableConfig, PmcData pmcData)
{
var questCount = repeatableConfig.NumQuests.GetValueOrDefault(0);
if (questCount == 0)
{
_logger.Warning($"Repeatable {repeatableConfig.Name} quests have a count of 0");
}
if (questCount == 0) _logger.Warning($"Repeatable {repeatableConfig.Name} quests have a count of 0");
// Add elite bonus to daily quests
if (repeatableConfig.Name.ToLower() == "daily" && _profileHelper.HasEliteSkillLevel(SkillTypes.Charisma, pmcData))
{
// Elite charisma skill gives extra daily quest(s)
questCount += _databaseService
.GetGlobals()
@@ -698,8 +637,13 @@ public class RepeatableQuestController(
.EliteBonusSettings
.RepeatableQuestExtraCount
.GetValueOrDefault(0);
}
// Prestige level 2 gives additional daily and weekly
// do the logic for all other than "daily_savage"
// use bigger than or equal incase modders add more
if (repeatableConfig.Name.ToLower() != "daily_savage" && pmcData.Info.PrestigeLevel >= 2)
questCount++;
return questCount;
}
}
+14 -39
View File
@@ -58,7 +58,7 @@ public class TradeController(
if (request.Type == "buy_from_trader")
{
var foundInRaid = _traderConfig.PurchasesAreFoundInRaid;
ProcessBuyTradeRequestData buyData = (ProcessBuyTradeRequestData)request;
var buyData = (ProcessBuyTradeRequestData)request;
_tradeHelper.buyItem(pmcData, buyData, sessionID, foundInRaid, output);
return output;
@@ -67,7 +67,7 @@ public class TradeController(
// Selling
if (request.Type == "sell_to_trader")
{
ProcessSellTradeRequestData sellData = (ProcessSellTradeRequestData)request;
var sellData = (ProcessSellTradeRequestData)request;
_tradeHelper.sellItem(pmcData, pmcData, sellData, sessionID, output);
return output;
@@ -97,13 +97,11 @@ public class TradeController(
{
var fleaOffer = _ragfairServer.GetOffer(offer.Id);
if (fleaOffer is null)
{
return _httpResponseUtil.AppendErrorToOutput(
output,
$"Offer with ID {offer.Id} not found",
BackendErrorCodes.OfferNotFound
);
}
if (offer.Count == 0)
{
@@ -115,19 +113,12 @@ public class TradeController(
}
if (_ragfairOfferHelper.OfferIsFromTrader(fleaOffer))
{
BuyTraderItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
}
else
{
BuyPmcItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
}
// Exit loop early if problem found
if (output.Warnings?.Count > 0)
{
return output;
}
if (output.Warnings?.Count > 0) return output;
}
return output;
@@ -152,17 +143,14 @@ public class TradeController(
if (PlayerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData))
{
var errorMessage = $"Unable to buy item: {fleaOffer.Items[0].Template} from trader: {fleaOffer.User.Id} as loyalty level too low, skipping";
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(errorMessage);
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug(errorMessage);
_httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable);
return;
}
ProcessBuyTradeRequestData buyData = new ProcessBuyTradeRequestData
var buyData = new ProcessBuyTradeRequestData
{
Action = "TradingConfirm",
Type = "buy_from_ragfair",
@@ -170,7 +158,7 @@ public class TradeController(
ItemId = fleaOffer.Root,
Count = requestOffer.Count,
SchemeId = 0,
SchemeItems = requestOffer.Items,
SchemeItems = requestOffer.Items
};
_tradeHelper.buyItem(pmcData, buyData, sessionId, _traderConfig.PurchasesAreFoundInRaid, output);
@@ -191,7 +179,7 @@ public class TradeController(
OfferRequest requestOffer,
ItemEventRouterResponse output)
{
ProcessBuyTradeRequestData buyData = new ProcessBuyTradeRequestData
var buyData = new ProcessBuyTradeRequestData
{
Action = "TradingConfirm",
Type = "buy_from_ragfair",
@@ -199,15 +187,12 @@ public class TradeController(
ItemId = fleaOffer.Id, // Store ragfair offerId in buyRequestData.item_id
Count = requestOffer.Count,
SchemeId = 0,
SchemeItems = requestOffer.Items,
SchemeItems = requestOffer.Items
};
// buyItem() must occur prior to removing the offer stack, otherwise item inside offer doesn't exist for confirmTrading() to use
_tradeHelper.buyItem(pmcData, buyData, sessionId, _ragfairConfig.Dynamic.PurchasesAreFoundInRaid, output);
if (output.Warnings?.Count > 0)
{
return;
}
if (output.Warnings?.Count > 0) return;
// resolve when a profile buy another profile's offer
var offerOwnerId = fleaOffer.User?.Id;
@@ -237,17 +222,12 @@ public class TradeController(
string? offerOwnerId)
{
// No ownerid, not player offer
if (offerOwnerId is null)
{
return false;
}
if (offerOwnerId is null) return false;
var offerCreatorProfile = _profileHelper.GetPmcProfile(offerOwnerId);
if (offerCreatorProfile is null || offerCreatorProfile.RagfairInfo.Offers?.Count == 0)
{
// No profile or no offers
return false;
}
// Does offer id exist in profile
return offerCreatorProfile.RagfairInfo.Offers.Any(offer => offer.Id == offerId);
@@ -296,17 +276,14 @@ public class TradeController(
int roublesToSend,
string trader)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Selling scav items to fence for {roublesToSend} roubles");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Selling scav items to fence for {roublesToSend} roubles");
// Create single currency item with all currency on it
Item rootCurrencyReward = new Item
var rootCurrencyReward = new Item
{
Id = _hashUtil.Generate(),
Template = Money.ROUBLES,
Upd = new Upd { StackObjectsCount = roublesToSend },
Upd = new Upd { StackObjectsCount = roublesToSend }
};
// Ensure money is properly split to follow its max stack size limit
@@ -317,7 +294,7 @@ public class TradeController(
sessionId,
_traderHelper.GetTraderById(trader).ToString(),
MessageType.MESSAGE_WITH_ITEMS,
_randomUtil.GetArrayValue((_databaseService.GetTrader(trader).Dialogue.TryGetValue("SoldItems", out var items)) ? items : new List<string>()),
_randomUtil.GetArrayValue(_databaseService.GetTrader(trader).Dialogue.TryGetValue("SoldItems", out var items) ? items : new List<string>()),
curencyReward.SelectMany(x => x).ToList(),
_timeUtil.GetHoursAsSeconds(72)
);
@@ -344,10 +321,8 @@ public class TradeController(
{
var itemDetails = _itemHelper.GetItem(itemToSell.Template);
if (!(itemDetails.Key && _itemHelper.IsOfBaseclasses(itemDetails.Value.Id, traderDetails.ItemsBuy.Category)))
{
// Skip if tpl isn't item OR item doesn't fulfil match traders buy categories
continue;
}
// Get price of item multiplied by how many are in stack
totalPrice += (int)((handbookPrices[itemToSell.Template] ?? 0) * (itemToSell.Upd?.StackObjectsCount ?? 1));
+6 -15
View File
@@ -59,17 +59,13 @@ public class TraderController(
// Adjust price by traderPriceMultipler config property
if (_traderConfig.TraderPriceMultipler != 1)
{
foreach (var kvp in trader.Value?.Assort?.BarterScheme)
{
var barterSchemeItem = kvp.Value[0][0];
if (barterSchemeItem != null && _paymentHelper.IsMoneyTpl(barterSchemeItem?.Template))
{
barterSchemeItem.Count += Math.Round((barterSchemeItem?.Count * _traderConfig?.TraderPriceMultipler) ?? 0D, 2);
}
barterSchemeItem.Count += Math.Round(barterSchemeItem?.Count * _traderConfig?.TraderPriceMultipler ?? 0D, 2);
}
}
// Create dict of pristine trader assorts on server start
if (_traderAssortService.GetPristineTraderAssort(trader.Key) == null)
@@ -81,7 +77,8 @@ public class TraderController(
_traderPurchasePersisterService.RemoveStalePurchasesFromProfiles(trader.Key);
// Set to next hour on clock or current time + 60 mins
trader.Value.Base.NextResupply = traderResetStartsWithServer ? (int)_traderHelper.GetNextUpdateTimestamp(trader.Value.Base.Id) : (int)nextHourTimestamp;
trader.Value.Base.NextResupply =
traderResetStartsWithServer ? (int)_traderHelper.GetNextUpdateTimestamp(trader.Value.Base.Id) : (int)nextHourTimestamp;
}
}
@@ -101,10 +98,7 @@ public class TraderController(
continue;
case Traders.FENCE:
{
if (_fenceService.NeedsPartialRefresh())
{
_fenceService.GenerateFenceAssorts();
}
if (_fenceService.NeedsPartialRefresh()) _fenceService.GenerateFenceAssorts();
continue;
}
}
@@ -135,10 +129,7 @@ public class TraderController(
{
traders.Add(_traderHelper.GetTrader(traderId, sessionId));
if (pmcData?.Info != null)
{
_traderHelper.LevelUp(traderId, pmcData);
}
if (pmcData?.Info != null) _traderHelper.LevelUp(traderId, pmcData);
}
traders.Sort(SortByTraderId);
@@ -186,7 +177,7 @@ public class TraderController(
{
var handbookPrices = _ragfairPriceService.GetAllStaticPrices();
return new()
return new GetItemPricesResponse
{
SupplyNextTime = _traderHelper.GetNextUpdateTimestamp(traderId),
Prices = handbookPrices,
@@ -23,10 +23,7 @@ public class WishlistController(
AddToWishlistRequest request,
string sessionId)
{
foreach (var item in request.Items)
{
pmcData.WishList.Dictionary.Add(item.Key, item.Value);
}
foreach (var item in request.Items) pmcData.WishList.Dictionary.Add(item.Key, item.Value);
return _eventOutputHolder.GetOutput(sessionId);
}
@@ -43,10 +40,7 @@ public class WishlistController(
RemoveFromWishlistRequest request,
string sessionId)
{
foreach (var itemId in request.Items)
{
pmcData.WishList.Dictionary.Remove(itemId);
}
foreach (var itemId in request.Items) pmcData.WishList.Dictionary.Remove(itemId);
return _eventOutputHolder.GetOutput(sessionId);
}
+1 -1
View File
@@ -4,4 +4,4 @@ public interface OnLoad
{
Task OnLoad();
string GetRoute();
}
}
+3 -14
View File
@@ -20,10 +20,7 @@ public abstract class Router
protected List<HandledRoute> GetInternalHandledRoutes()
{
if (handledRoutes.Count == 0)
{
handledRoutes = GetHandledRoutes();
}
if (handledRoutes.Count == 0) handledRoutes = GetHandledRoutes();
return handledRoutes;
}
@@ -31,11 +28,9 @@ public abstract class Router
public bool CanHandle(string url, bool partialMatch = false)
{
if (partialMatch)
{
return GetInternalHandledRoutes()
.Where((r) => r.dynamic)
.Any((r) => url.Contains(r.route));
}
return GetInternalHandledRoutes()
.Where((r) => !r.dynamic)
@@ -59,10 +54,7 @@ public abstract class StaticRouter : Router
var action = _actions.Single(route => route.url == url);
var type = action.bodyType;
IRequestData? info = null;
if (type != null && !string.IsNullOrEmpty(body))
{
info = (IRequestData?)_jsonUtil.Deserialize(body, type);
}
if (type != null && !string.IsNullOrEmpty(body)) info = (IRequestData?)_jsonUtil.Deserialize(body, type);
return action.action(url, info, sessionID, output);
}
@@ -88,10 +80,7 @@ public abstract class DynamicRouter : Router
var action = actions.First(r => url.Contains(r.url));
var type = action.bodyType;
IRequestData? info = null;
if (type != null && !string.IsNullOrEmpty(body))
{
info = (IRequestData?)_jsonUtil.Deserialize(body, type);
}
if (type != null && !string.IsNullOrEmpty(body)) info = (IRequestData?)_jsonUtil.Deserialize(body, type);
return action.action(url, info, sessionID, output);
}
@@ -58,9 +58,7 @@ public class BotEquipmentModGenerator(
// Get mod pool for the desired item
if (!settings.ModPool.TryGetValue(parentTemplate.Id, out var compatibleModsPool))
{
_logger.Warning($"bot: {settings.BotData.Role} lacks a mod slot pool for item: {parentTemplate.Id} {parentTemplate.Name}");
}
// Iterate over mod pool and choose mods to add to item
foreach (var (modSlotName, modPool) in compatibleModsPool ?? [])
@@ -93,16 +91,10 @@ public class BotEquipmentModGenerator(
);
// Rolled to skip mod and it shouldn't be force-spawned
if (modSpawnResult == ModSpawn.SKIP && !forceSpawn)
{
continue;
}
if (modSpawnResult == ModSpawn.SKIP && !forceSpawn) continue;
// Ensure submods for nvgs all spawn together
if (modSlotName == "mod_nvg")
{
forceSpawn = true;
}
if (modSlotName == "mod_nvg") forceSpawn = true;
// Get pool of items we can add for this slot
var modPoolToChooseFrom = modPool;
@@ -110,10 +102,8 @@ public class BotEquipmentModGenerator(
// Filter the pool of items in blacklist
var filteredModPool = FilterModsByBlacklist(modPoolToChooseFrom, specificBlacklist, modSlotName);
if (filteredModPool.Count > 0)
{
// use filtered pool as it has items in it
modPoolToChooseFrom = filteredModPool;
}
// Slot can hold armor plates + we are filtering possible items by bot level, handle
if (
@@ -131,11 +121,9 @@ public class BotEquipmentModGenerator(
{
case Result.UNKNOWN_FAILURE or Result.NO_DEFAULT_FILTER:
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Plate slot: {modSlotName} selection for armor: {parentTemplate.Id} failed: {plateSlotFilteringOutcome.Result}, skipping"
);
}
continue;
case Result.LACKS_PLATE_WEIGHTS:
@@ -172,10 +160,7 @@ public class BotEquipmentModGenerator(
}
// Compatible item not found + not required - skip
if (!(found || itemSlotTemplate.Required.GetValueOrDefault(false)))
{
continue;
}
if (!(found || itemSlotTemplate.Required.GetValueOrDefault(false))) continue;
// Get chosen mods db template and check it fits into slot
var modTemplate = _itemHelper.GetItem(modTpl);
@@ -188,9 +173,7 @@ public class BotEquipmentModGenerator(
settings.BotData.Role
)
)
{
continue;
}
// Generate new id to ensure all items are unique on bot
var modId = _hashUtil.Generate();
@@ -200,7 +183,6 @@ public class BotEquipmentModGenerator(
// Does item being added exist in mod pool - has its own mod pool
if (settings.ModPool.ContainsKey(modTpl))
{
// Call self again with mod being added as item to add child mods to
GenerateModsForEquipment(
equipment,
@@ -210,7 +192,6 @@ public class BotEquipmentModGenerator(
specificBlacklist,
forceSpawn
);
}
}
return equipment;
@@ -230,7 +211,7 @@ public class BotEquipmentModGenerator(
var result = new FilterPlateModsForSlotByLevelResult
{
Result = Result.UNKNOWN_FAILURE,
PlateModTemplates = null,
PlateModTemplates = null
};
// Not pmc or not a plate slot, return original mod pool array
@@ -299,29 +280,21 @@ public class BotEquipmentModGenerator(
chosenArmorPlateLevelString = chosenArmorPlateLevelDouble.ToString();
// New chosen plate class is higher than max, then set to min and check if valid
if (chosenArmorPlateLevelDouble > minMaxArmorPlateClass.Max)
{
chosenArmorPlateLevelString = minMaxArmorPlateClass.Min.ToString();
}
if (chosenArmorPlateLevelDouble > minMaxArmorPlateClass.Max) chosenArmorPlateLevelString = minMaxArmorPlateClass.Min.ToString();
findCompatiblePlateAttempts++;
platesOfDesiredLevel = platesFromDb.Where((item) => item.Properties.ArmorClass == chosenArmorPlateLevelDouble);
// Valid plates found, exit
if (platesOfDesiredLevel.Any())
{
break;
}
if (platesOfDesiredLevel.Any()) break;
// No valid plate class found in 3 tries, attempt default plates
if (findCompatiblePlateAttempts >= maxAttempts)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Plate filter too restrictive for armor: {armorItem.Name} {armorItem.Id}, unable to find plates of level: {chosenArmorPlateLevelString}, using items default plate"
);
}
var defaultPlate = GetDefaultPlateTpl(armorItem, modSlot);
if (defaultPlate is not null)
@@ -365,15 +338,9 @@ public class BotEquipmentModGenerator(
platePool.Sort(
(x, y) =>
{
if (x.Properties.ArmorClass < y.Properties.ArmorClass)
{
return -1;
}
if (x.Properties.ArmorClass < y.Properties.ArmorClass) return -1;
if (x.Properties.ArmorClass > y.Properties.ArmorClass)
{
return 1;
}
if (x.Properties.ArmorClass > y.Properties.ArmorClass) return 1;
return 0;
}
@@ -381,8 +348,8 @@ public class BotEquipmentModGenerator(
return new MinMax
{
Min = (platePool[0].Properties.ArmorClass),
Max = (platePool[platePool.Count - 1].Properties.ArmorClass),
Min = platePool[0].Properties.ArmorClass,
Max = platePool[platePool.Count - 1].Properties.ArmorClass
};
}
@@ -430,7 +397,7 @@ public class BotEquipmentModGenerator(
{
weaponName = request.ParentTemplate.Name,
weaponId = request.ParentTemplate.Id,
botRole = request.BotData.Role,
botRole = request.BotData.Role
}
)
);
@@ -469,7 +436,7 @@ public class BotEquipmentModGenerator(
modSlot = modSlot,
weaponId = request.ParentTemplate.Id,
weaponName = request.ParentTemplate.Name,
botRole = request.BotData.Role,
botRole = request.BotData.Role
}
)
);
@@ -484,10 +451,7 @@ public class BotEquipmentModGenerator(
request.ModSpawnChances,
botEquipConfig
);
if (modSpawnResult == ModSpawn.SKIP)
{
continue;
}
if (modSpawnResult == ModSpawn.SKIP) continue;
var isRandomisableSlot = randomisationSettings?.RandomisedWeaponModSlots?.Contains(modSlot) ?? false;
ModToSpawnRequest modToSpawnRequest = new()
@@ -509,15 +473,9 @@ public class BotEquipmentModGenerator(
var modToAdd = ChooseModToPutIntoSlot(modToSpawnRequest);
// Compatible mod not found
if (modToAdd is null)
{
continue;
}
if (modToAdd is null) continue;
if (!IsModValidForSlot(modToAdd, modsParentSlot, modSlot, request.ParentTemplate, request.BotData.Role))
{
continue;
}
if (!IsModValidForSlot(modToAdd, modsParentSlot, modSlot, request.ParentTemplate, request.BotData.Role)) continue;
var modToAddTemplate = modToAdd.Value;
// Skip adding mod to weapon if type limit reached
@@ -530,9 +488,7 @@ public class BotEquipmentModGenerator(
request.Weapon
)
)
{
continue;
}
// 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))
@@ -543,7 +499,6 @@ public class BotEquipmentModGenerator(
// Hydrate pool of mods that fit into mount as its a randomisable slot
if (isRandomisableSlot)
{
// Add scope mods to modPool dictionary to ensure the mount has a scope in the pool to pick
AddCompatibleModsForProvidedMod(
"mod_scope",
@@ -551,7 +506,6 @@ public class BotEquipmentModGenerator(
request.ModPool,
botEquipBlacklist
);
}
}
// If picked item is muzzle adapter that can hold a child, adjust spawn chance
@@ -572,14 +526,12 @@ public class BotEquipmentModGenerator(
// Handguard mod can take a sub handguard mod + weapon has no UBGL (takes same slot)
// 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") &&
!request.Weapon.Any((item) => item.SlotId == "mod_launcher")
)
{
modSlot == "mod_handguard" &&
modToAddTemplate.Value.Properties.Slots.Any((slot) => slot.Name == "mod_handguard") &&
!request.Weapon.Any((item) => item.SlotId == "mod_launcher")
)
// Needed for handguards with lower
request.ModSpawnChances["mod_handguard"] = 100;
}
// 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
@@ -594,13 +546,8 @@ public class BotEquipmentModGenerator(
if (_itemHelper.IsOfBaseclass(modToAddTemplate.Value.Id, BaseClasses.IRON_SIGHT))
{
if (modSlot == "mod_sight_front")
{
request.WeaponStats.HasFrontIronSight = true;
}
else if (modSlot == "mod_sight_rear")
{
request.WeaponStats.HasRearIronSight = true;
}
else if (modSlot == "mod_sight_rear") request.WeaponStats.HasRearIronSight = true;
}
else if (!(request.WeaponStats.HasOptic ?? false) && _itemHelper.IsOfBaseclass(modToAddTemplate.Value.Id, BaseClasses.SIGHTS))
{
@@ -620,10 +567,7 @@ public class BotEquipmentModGenerator(
);
// Update conflicting item list now item has been chosen
foreach (var conflictingItem in modToAddTemplate.Value.Properties.ConflictingItems)
{
request.ConflictingItemTpls.Add(conflictingItem);
}
foreach (var conflictingItem in modToAddTemplate.Value.Properties.ConflictingItems) request.ConflictingItemTpls.Add(conflictingItem);
// I first thought we could use the recursive generateModsForItems as previously for cylinder magazines.
// However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config
@@ -673,7 +617,7 @@ public class BotEquipmentModGenerator(
ParentTemplate = modToAddTemplate.Value,
ModSpawnChances = request.ModSpawnChances,
AmmoTpl = request.AmmoTpl,
BotData = new()
BotData = new BotData
{
Role = request.BotData.Role,
Level = request.BotData.Level,
@@ -725,10 +669,8 @@ public class BotEquipmentModGenerator(
{
// Gas block /w front sight is special case, deem it a 'front sight' too
if (modSlot == "mod_gas_block" && tpl == "5ae30e795acfc408fb139a0b")
{
// M4A1 front sight with gas block
return true;
}
return ((string[]) ["mod_sight_front", "mod_sight_rear"]).Contains(modSlot);
}
@@ -741,19 +683,17 @@ public class BotEquipmentModGenerator(
/// <returns>true if it can hold a scope</returns>
public bool ModSlotCanHoldScope(string modSlot, string modsParentId)
{
return (
((string[])
[
"mod_scope",
"mod_mount",
"mod_mount_000",
"mod_scope_000",
"mod_scope_001",
"mod_scope_002",
"mod_scope_003",
]).Contains(modSlot.ToLower()) &&
modsParentId == BaseClasses.MOUNT
);
return ((string[])
[
"mod_scope",
"mod_mount",
"mod_mount_000",
"mod_scope_000",
"mod_scope_001",
"mod_scope_002",
"mod_scope_003"
]).Contains(modSlot.ToLower()) &&
modsParentId == BaseClasses.MOUNT;
}
/// <summary>
@@ -778,10 +718,7 @@ public class BotEquipmentModGenerator(
return;
}
foreach (var modName in modSlotsToAdjust)
{
modSpawnChances[modName] = newChancePercent;
}
foreach (var modName in modSlotsToAdjust) modSpawnChances[modName] = newChancePercent;
}
/// <summary>
@@ -804,10 +741,7 @@ public class BotEquipmentModGenerator(
public List<string> SortModKeys(List<string> unsortedSlotKeys, string itemTplWithKeysToSort)
{
// No need to sort with only 1 item in array
if (unsortedSlotKeys.Count <= 1)
{
return unsortedSlotKeys;
}
if (unsortedSlotKeys.Count <= 1) return unsortedSlotKeys;
var isMount = _itemHelper.IsOfBaseclass(itemTplWithKeysToSort, BaseClasses.MOUNT);
@@ -940,17 +874,13 @@ public class BotEquipmentModGenerator(
{
var slotRequired = itemSlot.Required;
if (GetAmmoContainers().Contains(modSlotName))
{
// Always force mags/cartridges in weapon to spawn
return ModSpawn.SPAWN;
}
var spawnMod = _probabilityHelper.RollChance(modSpawnChances.GetValueOrDefault(modSlotName.ToLower()));
if (!spawnMod && (slotRequired.GetValueOrDefault(false) || (botEquipConfig.WeaponSlotIdsToMakeRequired?.Contains(modSlotName) ?? false)))
{
// Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot
return ModSpawn.DEFAULT_MOD;
}
return spawnMod ? ModSpawn.SPAWN : ModSpawn.SKIP;
}
@@ -967,10 +897,7 @@ public class BotEquipmentModGenerator(
var weaponTemplate = _itemHelper.GetItem(request.Weapon[0].Template).Value;
// It's ammo, use predefined ammo parameter
if (GetAmmoContainers().Contains(request.ModSlot) && request.ModSlot != "mod_magazine")
{
return _itemHelper.GetItem(request.AmmoTpl);
}
if (GetAmmoContainers().Contains(request.ModSlot) && request.ModSlot != "mod_magazine") return _itemHelper.GetItem(request.AmmoTpl);
// Ensure there's a pool of mods to pick from
var modPool = GetModPoolForSlot(request, weaponTemplate);
@@ -978,47 +905,35 @@ public class BotEquipmentModGenerator(
{
// Nothing in mod pool + item not required
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Mod pool for optional slot: {request.ModSlot} on item: {request.ParentTemplate.Name} was empty, skipping mod");
}
return null;
}
// Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements
if (request.ModSlot.Contains("mod_scope") && request.BotWeaponSightWhitelist is not null)
{
// scope pool has more than one scope
if (modPool.Count > 1)
{
modPool = FilterSightsByWeaponType(request.Weapon[0], modPool, request.BotWeaponSightWhitelist);
}
}
if (request.ModSlot == "mod_gas_block")
{
if (request.WeaponStats.HasOptic ?? false && modPool.Count > 1)
if (request.WeaponStats.HasOptic ?? (false && modPool.Count > 1))
{
// Attempt to limit modpool to low profile gas blocks when weapon has an optic
var onlyLowProfileGasBlocks = modPool.Where(
(tpl) =>
_botConfig.LowProfileGasBlockTpls.Contains(tpl)
);
if (onlyLowProfileGasBlocks.Count() > 0)
{
modPool = onlyLowProfileGasBlocks.ToHashSet();
}
if (onlyLowProfileGasBlocks.Count() > 0) modPool = onlyLowProfileGasBlocks.ToHashSet();
}
else if (request.WeaponStats.HasRearIronSight ?? false && modPool.Count() > 1)
else if (request.WeaponStats.HasRearIronSight ?? (false && modPool.Count() > 1))
{
// Attempt to limit modpool to high profile gas blocks when weapon has rear iron sight + no front iron sight
var onlyHighProfileGasBlocks = modPool.Where(
(tpl) => !_botConfig.LowProfileGasBlockTpls.Contains(tpl)
);
if (onlyHighProfileGasBlocks.Count() > 0)
{
modPool = onlyHighProfileGasBlocks.ToHashSet();
}
if (onlyHighProfileGasBlocks.Count() > 0) modPool = onlyHighProfileGasBlocks.ToHashSet();
}
}
@@ -1028,9 +943,7 @@ public class BotEquipmentModGenerator(
(request?.IsRandomisableSlot ?? false) &&
request.RandomisationSettings.MinimumMagazineSize is not null
)
{
modPool = GetFilterdMagazinePoolByCapacity(request, modPool).ToHashSet();
}
// Pick random mod that's compatible
var chosenModResult = GetCompatibleWeaponModTplForSlotFromPool(
@@ -1042,19 +955,13 @@ public class BotEquipmentModGenerator(
request.ModSlot
);
if (chosenModResult.SlotBlocked.GetValueOrDefault(false) && !parentSlot.Required.GetValueOrDefault(false))
{
// Don't bother trying to fit mod, slot is completely blocked
return null;
}
// Log if mod chosen was incompatible
if (chosenModResult.Incompatible.GetValueOrDefault(false) && !(parentSlot.Required.GetValueOrDefault(false)))
{
if (chosenModResult.Incompatible.GetValueOrDefault(false) && !parentSlot.Required.GetValueOrDefault(false))
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to find compatible mod of type: {parentSlot.Name}, in slot: {request.ModSlot} reason: {chosenModResult.Reason}");
}
}
// Get random mod to attach from items db for required slots if none found above
if (!(chosenModResult.Found ?? false) && parentSlot != null && (parentSlot.Required ?? false))
@@ -1064,19 +971,14 @@ public class BotEquipmentModGenerator(
}
// Compatible item not found + not required
if (!(chosenModResult.Found.GetValueOrDefault(false)) && parentSlot is not null && (!parentSlot.Required.GetValueOrDefault(false)))
{
return null;
}
if (!chosenModResult.Found.GetValueOrDefault(false) && parentSlot is not null && !parentSlot.Required.GetValueOrDefault(false)) return null;
if (!(chosenModResult.Found ?? false) && parentSlot is not null)
{
if (parentSlot.Required.GetValueOrDefault(false))
{
_logger.Warning(
$"Required slot unable to be filled, {request.ModSlot} on {request.ParentTemplate.Name} {request.ParentTemplate.Id} for weapon: {request.Weapon[0].Template}"
);
}
return null;
}
@@ -1105,7 +1007,7 @@ public class BotEquipmentModGenerator(
if (!desiredMagazineTpls.Any())
{
_logger.Warning($"Magazine size filter for { weaponTpl} was too strict, ignoring filter");
_logger.Warning($"Magazine size filter for {weaponTpl} was too strict, ignoring filter");
return modPool;
}
@@ -1130,21 +1032,17 @@ public class BotEquipmentModGenerator(
// Filter out incompatible mods from pool
var preFilteredModPool = GetFilteredModPool(modPool, request.ConflictingItemTpls);
if (preFilteredModPool.Count == 0)
{
return new()
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
Found = false,
Reason = $"Unable to add mod to {choiceTypeEnum.ToString()} slot: {modSlotName}. All: {modPool.Count()} had conflicts"
};
}
// Filter mod pool to only items that appear in parents allowed list
preFilteredModPool = preFilteredModPool.Where((tpl) => parentSlot.Props.Filters[0].Filter.Contains(tpl)).ToList();
if (preFilteredModPool.Count == 0)
{
return new() { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" };
}
return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" };
return GetCompatibleModFromPool(preFilteredModPool, choiceTypeEnum, weapon);
}
@@ -1166,7 +1064,7 @@ public class BotEquipmentModGenerator(
{
Incompatible = true,
Found = false,
Reason = "unknown",
Reason = "unknown"
};
// Limit how many attempts to find a compatible mod can occur before giving up
@@ -1178,16 +1076,12 @@ public class BotEquipmentModGenerator(
chosenTpl = exhaustableModPool.GetRandomValue();
var pickedItemDetails = _itemHelper.GetItem(chosenTpl);
if (!pickedItemDetails.Key)
{
// Not valid item, try again
continue;
}
if (pickedItemDetails.Value.Properties is null)
{
// no props data, try again
continue;
}
// Success - Default wanted + only 1 item in pool
if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count == 1)
@@ -1270,15 +1164,10 @@ public class BotEquipmentModGenerator(
public HashSet<string> GetModPoolForSlot(ModToSpawnRequest request, TemplateItem weaponTemplate)
{
// Mod is flagged as being default only, try and find it in globals
if (request.ModSpawnResult == ModSpawn.DEFAULT_MOD)
{
return GetModPoolForDefaultSlot(request, weaponTemplate);
}
if (request.ModSpawnResult == ModSpawn.DEFAULT_MOD) return GetModPoolForDefaultSlot(request, weaponTemplate);
if (request.IsRandomisableSlot.GetValueOrDefault(false))
{
return GetDynamicModPool(request.ParentTemplate.Id, request.ModSlot, request.BotEquipBlacklist);
}
// Required mod is not default or randomisable, use existing pool
return request.ItemModPool[request.ModSlot];
@@ -1290,12 +1179,8 @@ public class BotEquipmentModGenerator(
if (matchingModFromPreset is null)
{
if (request.ItemModPool[request.ModSlot]?.Count > 1)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"{request.BotData.Role} No default: {request.ModSlot} mod found for: {weaponTemplate.Name}, using existing pool");
}
}
// Couldn't find default in globals, use existing mod pool data
return request.ItemModPool[request.ModSlot];
@@ -1306,10 +1191,8 @@ public class BotEquipmentModGenerator(
// You'd have a mod being picked without any sub-mods in its chain, possibly resulting in missing required mods not being added
// Mod is in existing mod pool
if (request.ItemModPool[request.ModSlot].Contains(matchingModFromPreset.Template))
{
// Found mod on preset + it already exists in mod pool
return [matchingModFromPreset.Template];
}
// Get an array of items that are allowed in slot from parent item
// Check the filter of the slot to ensure a chosen mod fits
@@ -1325,36 +1208,26 @@ public class BotEquipmentModGenerator(
)
{
// Chosen mod has no conflicts + no children + is in parent compat list
if (!request.ConflictingItemTpls.Contains(matchingModFromPreset.Template))
{
return [matchingModFromPreset.Template];
}
if (!request.ConflictingItemTpls.Contains(matchingModFromPreset.Template)) return [matchingModFromPreset.Template];
// Above chosen mod had conflicts with existing weapon mods
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"{request.BotData.Role} Chosen default: {request.ModSlot} mod found for: {weaponTemplate.Name} weapon conflicts with item on weapon, cannot use default"
);
}
var existingModPool = request.ItemModPool[request.ModSlot];
if (existingModPool.Count == 1)
{
// The only item in pool isn't compatible
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"{request.BotData.Role} {request.ModSlot} Mod pool for: {weaponTemplate.Name} weapon has only incompatible items, using parent list instead"
);
}
// Last ditch, use full pool of items minus conflicts
var newListOfModsForSlot = parentSlotCompatibleItems.Where((tpl) => !request.ConflictingItemTpls.Contains(tpl));
if (newListOfModsForSlot.Count() > 0)
{
return newListOfModsForSlot.ToHashSet();
}
if (newListOfModsForSlot.Count() > 0) return newListOfModsForSlot.ToHashSet();
}
// Return full mod pool
@@ -1381,17 +1254,11 @@ public class BotEquipmentModGenerator(
{
// Edge case - using mp5sd reciever means default mp5 handguard doesn't fit
var isMp5sd = parentItemTpl == "5926f2e086f7745aae644231";
if (isMp5sd)
{
return _presetHelper.GetPreset("59411abb86f77478f702b5d2");
}
if (isMp5sd) return _presetHelper.GetPreset("59411abb86f77478f702b5d2");
// Edge case - dvl 500mm is the silenced barrel and has specific muzzle mods
var isDvl500mmSilencedBarrel = parentItemTpl == "5888945a2459774bf43ba385";
if (isDvl500mmSilencedBarrel)
{
return _presetHelper.GetPreset("59e8d2b386f77445830dd299");
}
if (isDvl500mmSilencedBarrel) return _presetHelper.GetPreset("59e8d2b386f77445830dd299");
return _presetHelper.GetDefaultPreset(weaponTemplate.Id);
}
@@ -1405,10 +1272,7 @@ public class BotEquipmentModGenerator(
public bool WeaponModComboIsIncompatible(List<Item> weapon, string modTpl)
{
// STM-9 + AR-15 Lone Star Ion Lite handguard
if (weapon[0].Template == "60339954d62c9b14ed777c06" && modTpl == "5d4405f0a4b9361e6a4e6bd9")
{
return true;
}
if (weapon[0].Template == "60339954d62c9b14ed777c06" && modTpl == "5d4405f0a4b9361e6a4e6bd9") return true;
return false;
}
@@ -1464,10 +1328,7 @@ public class BotEquipmentModGenerator(
while (exhaustableModPool.HasValues())
{
tmpModTpl = exhaustableModPool.GetRandomValue();
if (!_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(items, tmpModTpl, modSlot).Incompatible.GetValueOrDefault(false))
{
return tmpModTpl;
}
if (!_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(items, tmpModTpl, modSlot).Incompatible.GetValueOrDefault(false)) return tmpModTpl;
}
// No mod found
@@ -1497,14 +1358,11 @@ public class BotEquipmentModGenerator(
new
{
modId = modBeingAddedDbTemplate.Value?.Id ?? "UNKNOWN",
modSlot = modSlot,
modSlot = modSlot
}
)
);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Item -> {parentTemplate?.Id}; Slot -> {modSlot}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Item -> {parentTemplate?.Id}; Slot -> {modSlot}");
return false;
}
@@ -1514,7 +1372,6 @@ public class BotEquipmentModGenerator(
{
// Parent slot must be filled but db object is invalid, show warning and return false
if (slotAddedToTemplate.Required ?? false)
{
_logger.Warning(
_localisationService.GetText(
"bot-unable_to_add_mod_item_invalid",
@@ -1523,11 +1380,10 @@ public class BotEquipmentModGenerator(
itemName = modBeingAddedDbTemplate.Value?.Name ?? "UNKNOWN",
iodSlot = modSlot,
parentItemName = parentTemplate.Name,
botRole = botRole,
botRole = botRole
}
)
);
}
return false;
}
@@ -1548,21 +1404,14 @@ public class BotEquipmentModGenerator(
EquipmentFilterDetails botEquipBlacklist)
{
var desiredSlotObject = modTemplate.Properties.Slots?.FirstOrDefault((slot) => slot.Name.Contains(desiredSlotName));
if (desiredSlotObject is null)
{
return;
}
if (desiredSlotObject is null) return;
var supportedSubMods = desiredSlotObject.Props.Filters[0].Filter;
if (supportedSubMods is null)
{
return;
}
if (supportedSubMods is null) return;
// Filter mods
var filteredMods = FilterModsByBlacklist(supportedSubMods.ToHashSet(), botEquipBlacklist, desiredSlotName);
if (!filteredMods.Any())
{
_logger.Warning(
_localisationService
.GetText(
@@ -1570,11 +1419,10 @@ public class BotEquipmentModGenerator(
new
{
slotName = desiredSlotObject.Name,
itemName = modTemplate.Name,
itemName = modTemplate.Name
}
)
);
}
modPool.TryAdd(modTemplate.Id, new Dictionary<string, HashSet<string>>());
@@ -1615,10 +1463,7 @@ public class BotEquipmentModGenerator(
public HashSet<string> FilterModsByBlacklist(HashSet<string> allowedMods, EquipmentFilterDetails? botEquipBlacklist, string modSlot)
{
// No blacklist, nothing to filter out
if (botEquipBlacklist is null)
{
return allowedMods;
}
if (botEquipBlacklist is null) return allowedMods;
var result = new HashSet<string>();
@@ -1651,24 +1496,21 @@ public class BotEquipmentModGenerator(
new
{
weaponId = cylinderMagTemplate.Id,
weaponName = cylinderMagTemplate.Name,
weaponName = cylinderMagTemplate.Name
}
)
);
var camoraSlots = cylinderMagTemplate.Properties.Slots.Where((slot) => slot.Name.StartsWith("camora"));
// Attempt to generate camora slots for item
modPool[cylinderMagTemplate.Id] = new();
foreach (var camora in camoraSlots)
{
modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters?[0].Filter.ToHashSet();
}
modPool[cylinderMagTemplate.Id] = new Dictionary<string, HashSet<string>>();
foreach (var camora in camoraSlots) modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters?[0].Filter.ToHashSet();
itemModPool = modPool[cylinderMagTemplate.Id];
}
ExhaustableArray<string> exhaustableModPool = null;
string modSlot = "cartridges";
var modSlot = "cartridges";
const string camoraFirstSlot = "camora_000";
if (itemModPool.TryGetValue(modSlot, out var value))
{
@@ -1709,13 +1551,15 @@ public class BotEquipmentModGenerator(
{
var modSlotId = slot.Name;
var modId = _hashUtil.Generate();
items.Add(new()
{
Id = modId,
Template = modTpl,
ParentId = cylinderMagParentId,
SlotId = modSlotId
});
items.Add(
new Item
{
Id = modId,
Template = modTpl,
ParentId = cylinderMagParentId,
SlotId = modSlotId
}
);
}
}
@@ -1749,11 +1593,9 @@ public class BotEquipmentModGenerator(
if (!botWeaponSightWhitelist.TryGetValue(weaponDetails.Value.Parent, out var whitelistedSightTypes))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Unable to find whitelist for weapon type: {weaponDetails.Value.Parent} {weaponDetails.Value.Name}, skipping sight filtering"
);
}
return scopes;
}
@@ -1794,10 +1636,8 @@ public class BotEquipmentModGenerator(
)
) ??
false)
{
// Add mod to allowed list
filteredScopesAndMods.Add(item);
}
}
}
@@ -1805,9 +1645,7 @@ public class BotEquipmentModGenerator(
if (filteredScopesAndMods is null || filteredScopesAndMods.Count() == 0)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Scope whitelist too restrictive for: {weapon.Template} {weaponDetails.Value.Name}, skipping filter");
}
return scopes;
}
+26 -79
View File
@@ -66,7 +66,7 @@ public class BotGenerator(
BotRelativeLevelDeltaMin = 0,
BotCountToGenerate = 1,
BotDifficulty = difficulty,
IsPlayerScav = true,
IsPlayerScav = true
};
bot = GenerateBot(sessionId, bot, botTemplate, botGenDetails);
@@ -128,10 +128,7 @@ public class BotGenerator(
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
: botGenerationDetails.Role;
var botJsonTemplateClone = _cloner.Clone(_botHelper.GetBotTemplate(botRole));
if (botJsonTemplateClone is null)
{
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
}
if (botJsonTemplateClone is null) _logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
}
@@ -185,14 +182,12 @@ public class BotGenerator(
// Only filter bot equipment, never players
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false))
{
_botEquipmentFilterService.FilterBotEquipment(
sessionId,
botJsonTemplate,
botLevel.Level.Value,
botGenerationDetails
);
}
bot.Info.Nickname = _botNameService.GenerateUniqueBotNickname(
botJsonTemplate,
@@ -214,24 +209,17 @@ public class BotGenerator(
}
if (!_seasonalEventService.ChristmasEventEnabled())
{
// Process all bots EXCEPT gifter, he needs christmas items
if (botGenerationDetails.Role != "gifter")
{
_seasonalEventService.RemoveChristmasItemsFromBotInventory(
botJsonTemplate.BotInventory,
botGenerationDetails.Role
);
}
}
RemoveBlacklistedLootFromBotTemplate(botJsonTemplate.BotInventory);
// Remove hideout data if bot is not a PMC or pscav - match what live sends
if (!(botGenerationDetails.IsPmc.GetValueOrDefault(false) || botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)))
{
bot.Hideout = null;
}
if (!(botGenerationDetails.IsPmc.GetValueOrDefault(false) || botGenerationDetails.IsPlayerScav.GetValueOrDefault(false))) bot.Hideout = null;
bot.Info.Experience = botLevel.Exp;
bot.Info.Level = botLevel.Level;
@@ -260,10 +248,7 @@ public class BotGenerator(
{
bot.Info.IsStreamerModeAvailable = true; // Set to true so client patches can pick it up later - client sometimes alters botrole to assaultGroup
SetRandomisedGameVersionAndCategory(bot.Info);
if (bot.Info.GameVersion == GameEditions.UNHEARD)
{
AddAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
}
if (bot.Info.GameVersion == GameEditions.UNHEARD) AddAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
}
// Add drip
@@ -281,10 +266,7 @@ public class BotGenerator(
bot.Info.GameVersion
);
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase))
{
AddDogtagToBot(bot);
}
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase)) AddDogtagToBot(bot);
// Generate new bot ID
AddIdsToBot(bot, botGenerationDetails);
@@ -293,10 +275,7 @@ public class BotGenerator(
GenerateInventoryId(bot);
// Set role back to originally requested now its been generated
if (botGenerationDetails.EventRole is not null)
{
bot.Info.Settings.Role = botGenerationDetails.EventRole;
}
if (botGenerationDetails.EventRole is not null) bot.Info.Settings.Role = botGenerationDetails.EventRole;
return bot;
}
@@ -320,13 +299,9 @@ public class BotGenerator(
/// <returns>Experience for kill</returns>
public double GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax> experiences, string botDifficulty, string role)
{
if (!experiences.TryGetValue(botDifficulty.ToLower(), out var result))
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`");
return _randomUtil.GetDouble(experiences["normal"].Min.Value, experiences["normal"].Max.Value);
}
@@ -385,20 +360,16 @@ public class BotGenerator(
);
if (blacklist?.Gear is null)
{
// Nothing to filter by
return;
}
foreach (var (equipmentSlot, blacklistedTpls) in blacklist.Gear)
{
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentSlot];
foreach (var blacklistedTpl in blacklistedTpls)
{
// Set weighting to 0, will never be picked
equipmentDict[blacklistedTpl] = 0;
}
}
}
@@ -430,24 +401,14 @@ public class BotGenerator(
var propValue = (Dictionary<string, double>)prop.GetValue(botInventory.Items);
// No container, skip
if (propValue?.Count == 0)
{
continue;
}
if (propValue?.Count == 0) continue;
List<string> tplsToRemove = [];
foreach (var (key, _) in propValue)
{
if (_itemFilterService.IsLootableItemBlacklisted(key))
{
tplsToRemove.Add(key);
}
}
foreach (var blacklistedTplToRemove in tplsToRemove)
{
propValue.Remove(blacklistedTplToRemove);
}
foreach (var blacklistedTplToRemove in tplsToRemove) propValue.Remove(blacklistedTplToRemove);
prop.SetValue(botInventory.Items, propValue);
}
@@ -483,10 +444,7 @@ public class BotGenerator(
public void LogPmcGeneratedCount(List<BotBase> output)
{
var pmcCount = output.Aggregate(0, (acc, cur) => { return cur.Info.Side is "Bear" or "Usec" ? acc + 1 : acc; });
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Generated {output.Count} total bots. Replaced {pmcCount} with PMCs");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generated {output.Count} total bots. Replaced {pmcCount} with PMCs");
}
/// <summary>
@@ -503,17 +461,17 @@ public class BotGenerator(
BotBaseHealth health = new()
{
Hydration = new()
Hydration = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)healthObj.Hydration.Min, (int)healthObj.Hydration.Max),
Maximum = healthObj.Hydration.Max
},
Energy = new()
Energy = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)healthObj.Energy.Min, (int)healthObj.Energy.Max),
Maximum = healthObj.Energy.Max
},
Temperature = new()
Temperature = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)healthObj.Temperature.Min, (int)healthObj.Temperature.Max),
Maximum = healthObj.Temperature.Max
@@ -523,7 +481,7 @@ public class BotGenerator(
{
"Head", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.Head.Min, (int)bodyParts.Head.Max),
Maximum = Math.Round(bodyParts.Head.Max ?? 0)
@@ -533,7 +491,7 @@ public class BotGenerator(
{
"Chest", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.Chest.Min, (int)bodyParts.Chest.Max),
Maximum = Math.Round(bodyParts.Chest.Max ?? 0)
@@ -543,7 +501,7 @@ public class BotGenerator(
{
"Stomach", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.Stomach.Min, (int)bodyParts.Stomach.Max),
Maximum = Math.Round(bodyParts.Stomach.Max ?? 0)
@@ -553,7 +511,7 @@ public class BotGenerator(
{
"LeftArm", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.LeftArm.Min, (int)bodyParts.LeftArm.Max),
Maximum = Math.Round(bodyParts.LeftArm.Max ?? 0)
@@ -563,7 +521,7 @@ public class BotGenerator(
{
"RightArm", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.RightArm.Min, (int)bodyParts.RightArm.Max),
Maximum = Math.Round(bodyParts.RightArm.Max ?? 0)
@@ -573,7 +531,7 @@ public class BotGenerator(
{
"LeftLeg", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.LeftLeg.Min, (int)bodyParts.LeftLeg.Max),
Maximum = Math.Round(bodyParts.LeftLeg.Max ?? 0)
@@ -583,7 +541,7 @@ public class BotGenerator(
{
"RightLeg", new BodyPartHealth
{
Health = new()
Health = new CurrentMinMax
{
Current = _randomUtil.GetInt((int)bodyParts.RightLeg.Min, (int)bodyParts.RightLeg.Max),
Maximum = Math.Round(bodyParts.RightLeg.Max ?? 0)
@@ -665,10 +623,7 @@ public class BotGenerator(
{
// Get skill from dict, skip if not found
var skill = kvp.Value;
if (skill == null)
{
return null;
}
if (skill == null) return null;
// All skills have id and progress props
var skillToAdd = new BaseSkill
@@ -726,16 +681,10 @@ public class BotGenerator(
// Optimisation - skip items without a parentId
// They are never linked to root inventory item + we already handled root item above
if (item.ParentId is null)
{
continue;
}
if (item.ParentId is null) continue;
// Item is a child of root inventory item, update its parentId value to newly generated id
if (item.ParentId == profile.Inventory.Equipment)
{
item.ParentId = newInventoryItemId;
}
if (item.ParentId == profile.Inventory.Equipment) item.ParentId = newInventoryItemId;
}
// Update inventory equipment id to new one we generated
@@ -797,10 +746,10 @@ public class BotGenerator(
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
ParentId = bot.Inventory.Equipment,
SlotId = "Dogtag",
Upd = new()
Upd = new Upd
{
SpawnedInSession = true,
},
SpawnedInSession = true
}
};
bot.Inventory.Items.Add(inventoryItem);
@@ -815,7 +764,6 @@ public class BotGenerator(
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
{
if (side.ToLower() == "usec")
{
switch (gameVersion)
{
case GameEditions.EDGE_OF_DARKNESS:
@@ -825,7 +773,6 @@ public class BotGenerator(
default:
return ItemTpl.BARTER_DOGTAG_USEC;
}
}
switch (gameVersion)
{
@@ -40,7 +40,7 @@ public class BotInventoryGenerator(
private BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
// Slots handled individually inside `GenerateAndAddEquipmentToBot`
List<EquipmentSlots> _excludedEquipmentSlots =
private List<EquipmentSlots> _excludedEquipmentSlots =
[
EquipmentSlots.Pockets,
EquipmentSlots.FirstPrimaryWeapon,
@@ -123,12 +123,12 @@ public class BotInventoryGenerator(
{
Items =
[
new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
new() { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
new() { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
new() { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
new() { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE },
new() { Id = hideoutCustomizationStashId, Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION }
new Item { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
new Item { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
new Item { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
new Item { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
new Item { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE },
new Item { Id = hideoutCustomizationStashId, Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION }
],
Equipment = equipmentId,
Stash = stashId,
@@ -138,7 +138,7 @@ public class BotInventoryGenerator(
HideoutAreaStashes = new Dictionary<string, string>(),
FastPanel = new Dictionary<string, string>(),
FavoriteItems = [],
HideoutCustomizationStashId = hideoutCustomizationStashId,
HideoutCustomizationStashId = hideoutCustomizationStashId
};
}
@@ -165,16 +165,12 @@ public class BotInventoryGenerator(
raidConfig is not null &&
_weatherHelper.IsNightTime(raidConfig.TimeVariant)
)
{
foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers))
{
foreach (var equipmentSlotKvP in randomistionDetails.NighttimeChanges.EquipmentModsModifiers)
// Never let mod chance go outside of 0 - 100
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
Math.Max(randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0),
100
);
}
}
// Get profile of player generating bots, we use their level later on
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
@@ -188,10 +184,7 @@ public class BotInventoryGenerator(
{
// Skip some slots as they need to be done in a specific order + with specific parameter values
// e.g. Weapons
if (_excludedEquipmentSlots.Contains(equipmentSlot))
{
continue;
}
if (_excludedEquipmentSlots.Contains(equipmentSlot)) continue;
GenerateEquipment(
new GenerateEquipmentProperties
@@ -223,7 +216,7 @@ public class BotInventoryGenerator(
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE, ItemTpl.POCKETS_LARGE],
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
@@ -238,7 +231,7 @@ public class BotInventoryGenerator(
Inventory = botInventory,
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
@@ -253,7 +246,7 @@ public class BotInventoryGenerator(
Inventory = botInventory,
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
@@ -268,7 +261,7 @@ public class BotInventoryGenerator(
Inventory = botInventory,
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
@@ -283,29 +276,22 @@ public class BotInventoryGenerator(
Inventory = botInventory,
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
// Bot has no armor vest and flagged to be forced to wear armored rig in this event
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest)
{
// Filter rigs down to only those with armor
FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole);
}
// Optimisation - Remove armored rigs from pool
if (hasArmorVest)
{
// Filter rigs down to only those with armor
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
}
// Bot is flagged as always needing a vest
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest)
{
wornItemChances.EquipmentChances["TacticalVest"] = 100;
}
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) wornItemChances.EquipmentChances["TacticalVest"] = 100;
GenerateEquipment(
new GenerateEquipmentProperties
@@ -318,7 +304,7 @@ public class BotInventoryGenerator(
Inventory = botInventory,
BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
}
);
}
@@ -343,10 +329,7 @@ public class BotInventoryGenerator(
if (!tacVestsWithArmor.Any())
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
return;
}
@@ -369,10 +352,7 @@ public class BotInventoryGenerator(
if (!allowEmptyResult && !tacVestsWithoutArmor.Any())
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
return;
}
@@ -416,10 +396,7 @@ public class BotInventoryGenerator(
var attempts = 0;
while (!found)
{
if (!settings.RootEquipmentPool.Any())
{
return false;
}
if (!settings.RootEquipmentPool.Any()) return false;
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool);
var dbResult = _itemHelper.GetItem(chosenItemTpl);
@@ -427,10 +404,7 @@ public class BotInventoryGenerator(
if (!dbResult.Key)
{
_logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl));
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
// Remove picked item
settings.RootEquipmentPool.Remove(chosenItemTpl);
@@ -449,10 +423,7 @@ public class BotInventoryGenerator(
if (compatibilityResult.Incompatible ?? false)
{
// Tried x different items that failed, stop
if (attempts > maxAttempts)
{
return false;
}
if (attempts > maxAttempts) return false;
// Remove picked item from pool
settings.RootEquipmentPool.Remove(chosenItemTpl);
@@ -488,17 +459,14 @@ public class BotInventoryGenerator(
if (_botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole) &&
settings.RandomisationDetails?.RandomisedArmorSlots != null &&
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))
{
// Filter out mods from relevant blacklist
settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem(
pickedItemDb.Id,
botEquipBlacklist.Equipment
);
}
var itemIsOnGenerateModBlacklist = settings.GenerateModsBlacklist != null && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id);
// Does item have slots for sub-mods to be inserted into
if (pickedItemDb.Properties?.Slots?.Count > 0
&& !itemIsOnGenerateModBlacklist)
if (pickedItemDb.Properties?.Slots?.Count > 0 && !itemIsOnGenerateModBlacklist)
{
var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment(
[item],
@@ -533,13 +501,11 @@ public class BotInventoryGenerator(
foreach (var modSlot in modPool)
{
// Get blacklist
if (!equipmentBlacklist.TryGetValue(modSlot.Key, out var blacklistedMods))
{
blacklistedMods = [];
};
if (!equipmentBlacklist.TryGetValue(modSlot.Key, out var blacklistedMods)) blacklistedMods = [];
;
// Get mods not on blacklist
var filteredMods = modPool[modSlot.Key].Where((slotName) => !(blacklistedMods).Contains(slotName));
var filteredMods = modPool[modSlot.Key].Where((slotName) => !blacklistedMods.Contains(slotName));
if (!filteredMods.Any())
{
_logger.Warning($"Filtering {modSlot.Key} pool resulting in 0 items, skipping filter");
@@ -568,10 +534,8 @@ public class BotInventoryGenerator(
{
var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances);
foreach (var desiredWeapons in weaponSlotsToFill)
{
// Add weapon to bot if true and bot json has something to put into the slot
if (desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any())
{
AddWeaponAndMagazinesToInventory(
sessionId,
desiredWeapons,
@@ -583,8 +547,6 @@ public class BotInventoryGenerator(
itemGenerationLimitsMinMax,
botLevel
);
}
}
}
/// <summary>
@@ -597,16 +559,16 @@ public class BotInventoryGenerator(
var shouldSpawnPrimary = _randomUtil.GetChance100(equipmentChances.EquipmentChances["FirstPrimaryWeapon"]);
return
[
new()
new DesiredWeapons
{
Slot = EquipmentSlots.FirstPrimaryWeapon, ShouldSpawn = shouldSpawnPrimary
},
new()
new DesiredWeapons
{
Slot = EquipmentSlots.SecondPrimaryWeapon,
ShouldSpawn = shouldSpawnPrimary && _randomUtil.GetChance100(equipmentChances.EquipmentChances["SecondPrimaryWeapon"])
},
new()
new DesiredWeapons
{
Slot = EquipmentSlots.Holster,
ShouldSpawn = !shouldSpawnPrimary || _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]) // No primary = force pistol
+4 -13
View File
@@ -27,30 +27,21 @@ public class BotLevelGenerator(
/// <returns>IRandomisedBotLevelResult object</returns>
public RandomisedBotLevelResult GenerateBotLevel(MinMax levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
{
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false))
{
return new RandomisedBotLevelResult { Exp = 0, Level = 1 };
}
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false)) return new RandomisedBotLevelResult { Exp = 0, Level = 1 };
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
var botLevelRange = GetRelativePmcBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
// Get random level based on the exp table.
int exp = 0;
var exp = 0;
var level = int.Parse(
ChooseBotLevel(botLevelRange.Min.Value, botLevelRange.Max.Value, 1, 1.15)
.ToString()
); // TODO - nasty double to string to int conversion
for (var i = 0; i < level; i++)
{
exp += expTable[i].Experience.Value;
}
for (var i = 0; i < level; i++) exp += expTable[i].Experience.Value;
// Sprinkle in some random exp within the level, unless we are at max level.
if (level < expTable.Length - 1)
{
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
}
if (level < expTable.Length - 1) exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
return new RandomisedBotLevelResult { Level = level, Exp = exp };
}
+23 -95
View File
@@ -47,10 +47,7 @@ public class BotLootGenerator(
// Clone limits and set all values to 0 to use as a running total
var limitsForBotDict = _cloner.Clone(limits);
// Init current count of items we want to limit
foreach (var limit in limitsForBotDict)
{
limitsForBotDict[limit.Key] = 0;
}
foreach (var limit in limitsForBotDict) limitsForBotDict[limit.Key] = 0;
return new ItemSpawnLimitSettings
{
@@ -113,10 +110,7 @@ public class BotLootGenerator(
}
// Forced pmc healing loot into secure container
if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure)
{
AddForcedMedicalItemsToPmcSecure(botInventory, botRole);
}
if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure) AddForcedMedicalItemsToPmcSecure(botInventory, botRole);
var botItemLimits = GetItemSpawnLimitsForBot(botRole);
@@ -235,7 +229,6 @@ public class BotLootGenerator(
{
// Add randomly generated weapon to PMC backpacks
if (isPmc && _randomUtil.GetChance100(_pmcConfig.LooseWeaponInBackpackChancePercent))
{
AddLooseWeaponsToInventorySlot(
sessionId,
botInventory,
@@ -247,7 +240,6 @@ public class BotLootGenerator(
botLevel,
filledContainerIds
);
}
var backpackLootRoubleTotal = GetBackpackRoubleTotalByLevel(botLevel, isPmc);
AddLootFromPool(
@@ -271,7 +263,6 @@ public class BotLootGenerator(
// TacticalVest - generate loot if they have one
if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest))
{
// Vest
AddLootFromPool(
_botLootCacheService.GetLootFromCache(
@@ -290,7 +281,6 @@ public class BotLootGenerator(
isPmc,
filledContainerIds
);
}
// Pockets
AddLootFromPool(
@@ -315,7 +305,6 @@ public class BotLootGenerator(
// only add if not a pmc or is pmc and flag is true
if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig))
{
AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate),
[EquipmentSlots.SecuredContainer],
@@ -327,7 +316,6 @@ public class BotLootGenerator(
isPmc,
filledContainerIds
);
}
}
private MinMaxLootItemValue? GetSingleItemLootPriceLimits(int botLevel, bool isPmc)
@@ -368,15 +356,9 @@ public class BotLootGenerator(
{
List<EquipmentSlots> result = [EquipmentSlots.Pockets];
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString()))
{
result.Add(EquipmentSlots.TacticalVest);
}
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString())) result.Add(EquipmentSlots.TacticalVest);
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString()))
{
result.Add(EquipmentSlots.Backpack);
}
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString())) result.Add(EquipmentSlots.Backpack);
return result;
}
@@ -449,10 +431,7 @@ public class BotLootGenerator(
for (var i = 0; i < totalItemCount; i++)
{
// Pool can become empty if item spawn limits keep removing items
if (pool.Count == 0)
{
return;
}
if (pool.Count == 0) return;
var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool);
var (key, itemToAddTemplate) = _itemHelper.GetItem(weightedItemTpl);
@@ -481,7 +460,7 @@ public class BotLootGenerator(
Id = newRootItemId,
Template = itemToAddTemplate?.Id ?? string.Empty,
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemToAddTemplate, botRole)
},
}
];
// Is Simple-Wallet / WZ wallet
@@ -505,14 +484,12 @@ public class BotLootGenerator(
{
// Add each currency to wallet
foreach (var itemToAdd in itemsToAdd)
{
_inventoryHelper.PlaceItemInContainer(
containerGrid,
itemToAdd,
itemWithChildrenToAdd[0].Id,
"main"
);
}
itemWithChildrenToAdd.AddRange(itemsToAdd.SelectMany(x => x));
}
@@ -538,24 +515,20 @@ public class BotLootGenerator(
if (itemAddedResult == ItemAddedResult.NO_CONTAINERS)
{
// Bot has no container to put item in, exit
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them");
}
break;
}
fitItemIntoContainerAttempts++;
if (fitItemIntoContainerAttempts >= 4)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Failed placing item: {itemToAddTemplate.Id} - {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} " +
$"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " +
$"times, reason: {itemAddedResult}, skipping"
);
}
break;
}
@@ -571,10 +544,7 @@ public class BotLootGenerator(
if (totalValueLimitRub > 0)
{
currentTotalRub += _handbookHelper.GetTemplatePrice(itemToAddTemplate.Id);
if (currentTotalRub > totalValueLimitRub)
{
break;
}
if (currentTotalRub > totalValueLimitRub) break;
}
}
}
@@ -599,12 +569,12 @@ public class BotLootGenerator(
var chosenStackCount = _weightedRandomHelper.GetWeightedValue<string>(_botConfig.WalletLoot.StackSizeWeight);
List<Item> items =
[
new Item
new()
{
Id = _hashUtil.Generate(),
Template = _weightedRandomHelper.GetWeightedValue<string>(_botConfig.WalletLoot.CurrencyWeight),
ParentId = walletId,
Upd = new()
Upd = new Upd
{
StackObjectsCount = int.Parse(chosenStackCount)
}
@@ -627,24 +597,15 @@ public class BotLootGenerator(
{
// Fill ammo box
if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX))
{
_itemHelper.AddCartridgesToAmmoBox(itemToAddChildrenTo, itemToAddTemplate);
}
// Make money a stack
else if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.MONEY))
{
RandomiseMoneyStackSize(botRole, itemToAddTemplate, itemToAddChildrenTo[0]);
}
// Make ammo a stack
else if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO))
{
RandomiseAmmoStackSize(isPmc, itemToAddTemplate, itemToAddChildrenTo[0]);
}
// Must add soft inserts/plates
else if (_itemHelper.ItemRequiresSoftInserts(itemToAddTemplate.Id))
{
_itemHelper.AddChildSlotItems(itemToAddChildrenTo, itemToAddTemplate, null, false);
}
else if (_itemHelper.ItemRequiresSoftInserts(itemToAddTemplate.Id)) _itemHelper.AddChildSlotItems(itemToAddChildrenTo, itemToAddTemplate, null, false);
}
/// <summary>
@@ -682,10 +643,7 @@ public class BotLootGenerator(
(int)_pmcConfig.LooseWeaponInBackpackLootMinMax.Max
);
if (randomisedWeaponCount <= 0)
{
return;
}
if (randomisedWeaponCount <= 0) return;
for (var i = 0; i < randomisedWeaponCount; i++)
{
@@ -709,12 +667,8 @@ public class BotLootGenerator(
);
if (result != ItemAddedResult.SUCCESS)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Failed to add additional weapon {generatedWeapon.Weapon[0].Id} to bot backpack, reason: {result.ToString()}");
}
}
}
}
@@ -729,33 +683,24 @@ public class BotLootGenerator(
{
// PMCs and scavs have different sections of bot config for spawn limits
if (itemSpawnLimits is not null && itemSpawnLimits.GlobalLimits?.Count == 0)
{
// No items found in spawn limit, drop out
return false;
}
// No spawn limits, skipping
if (itemSpawnLimits is null)
{
return false;
}
if (itemSpawnLimits is null) return false;
var idToCheckFor = GetMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.GlobalLimits);
if (idToCheckFor is null)
{
// ParentId or tplid not found in spawnLimits, not a spawn limited item, skip
return false;
}
// Use tryAdd to see if it exists, and automatically add 1
if (!itemSpawnLimits.CurrentLimits.TryAdd(idToCheckFor, 1))
{
// if it does exist, come in here and increment
// Increment item count with this bot type
itemSpawnLimits.CurrentLimits[idToCheckFor]++;
}
// Check if over limit
var currentLimitCount = itemSpawnLimits.CurrentLimits[idToCheckFor];
@@ -764,8 +709,7 @@ public class BotLootGenerator(
// Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely
if (currentLimitCount > currentLimitCount * 10)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
_localisationService.GetText(
"bot-item_spawn_limit_reached_skipping_item",
@@ -777,7 +721,6 @@ public class BotLootGenerator(
}
)
);
}
return false;
}
@@ -797,10 +740,7 @@ public class BotLootGenerator(
public void RandomiseMoneyStackSize(string botRole, TemplateItem itemTemplate, Item moneyItem)
{
// Get all currency weights for this bot type
if (!_botConfig.CurrencyStackSize.TryGetValue(botRole, out var currencyWeights))
{
currencyWeights = _botConfig.CurrencyStackSize["default"];
}
if (!_botConfig.CurrencyStackSize.TryGetValue(botRole, out var currencyWeights)) currencyWeights = _botConfig.CurrencyStackSize["default"];
var currencyWeight = currencyWeights[moneyItem.Template];
@@ -831,19 +771,13 @@ public class BotLootGenerator(
/// <returns>Dictionary of tplIds and limit</returns>
public Dictionary<string, double> GetItemSpawnLimitsForBotType(string botRole)
{
if (_botHelper.IsBotPmc(botRole))
{
return _botConfig.ItemSpawnLimits["pmc"];
}
if (_botHelper.IsBotPmc(botRole)) return _botConfig.ItemSpawnLimits["pmc"];
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLower()))
{
return _botConfig.ItemSpawnLimits[botRole.ToLower()];
}
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLower())) return _botConfig.ItemSpawnLimits[botRole.ToLower()];
_logger.Warning(_localisationService.GetText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole));
return new();
return new Dictionary<string, double>();
}
/// <summary>
@@ -854,16 +788,10 @@ public class BotLootGenerator(
/// <returns>id as string, otherwise undefined</returns>
public string? GetMatchingIdFromSpawnLimits(TemplateItem itemTemplate, Dictionary<string, double> spawnLimits)
{
if (spawnLimits.ContainsKey(itemTemplate.Id))
{
return itemTemplate.Id;
}
if (spawnLimits.ContainsKey(itemTemplate.Id)) return itemTemplate.Id;
// tplId not found in spawnLimits, check if parentId is
if (spawnLimits.ContainsKey(itemTemplate.Parent))
{
return itemTemplate.Parent;
}
if (spawnLimits.ContainsKey(itemTemplate.Parent)) return itemTemplate.Parent;
// parentId and tplId not found
return null;
+32 -86
View File
@@ -139,10 +139,8 @@ public class BotWeaponGenerator(
// Chance to add randomised weapon enhancement
if (isPmc && _randomUtil.GetChance100(_pmcConfig.WeaponHasEnhancementChancePercent))
{
// Add buff to weapon root
_repairService.AddBuff(_repairConfig.RepairKit.Weapon, weaponWithModsArray[0]);
}
// Add mods to weapon base
if (modPool.Keys.Contains(weaponTpl))
@@ -161,10 +159,10 @@ public class BotWeaponGenerator(
ParentTemplate = weaponItemTemplate,
ModSpawnChances = modChances,
AmmoTpl = ammoTpl,
BotData = new() { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
ModLimits = modLimits,
WeaponStats = new(),
ConflictingItemTpls = new(),
WeaponStats = new WeaponStats(),
ConflictingItemTpls = new HashSet<string>()
};
weaponWithModsArray = _botEquipmentModGenerator.GenerateModsForWeapon(
sessionId,
@@ -174,7 +172,6 @@ public class BotWeaponGenerator(
// Use weapon preset from globals.json if weapon isn't valid
if (!IsWeaponValid(weaponWithModsArray, botRole))
{
// Weapon is bad, fall back to weapons preset
weaponWithModsArray = GetPresetWeaponMods(
weaponTpl,
@@ -183,19 +180,15 @@ public class BotWeaponGenerator(
weaponItemTemplate,
botRole
);
}
var tempList = _cloner.Clone(weaponWithModsArray.Where((item) => item.SlotId == _modMagazineSlotId));
// Fill existing magazines to full and sync ammo type
foreach (var magazine in tempList)
{
FillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
}
foreach (var magazine in tempList) FillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
// Add cartridge(s) to gun chamber(s)
if (weaponItemTemplate.Properties.Chambers?.Any() ??
false &&
weaponItemTemplate.Properties.Chambers[0].Props.Filters[0].Filter.Contains(ammoTpl))
(false &&
weaponItemTemplate.Properties.Chambers[0].Props.Filters[0].Filter.Contains(ammoTpl)))
{
// Guns have variety of possible Chamber ids, patron_in_weapon/patron_in_weapon_000/patron_in_weapon_001
var chamberSlotNames = weaponItemTemplate.Properties.Chambers.Select((chamberSlot) => chamberSlot.Name);
@@ -212,13 +205,13 @@ public class BotWeaponGenerator(
FillUbgl(weaponWithModsArray, ubglMod, ubglAmmoTpl);
}
return new()
return new GenerateWeaponResult
{
Weapon = weaponWithModsArray,
ChosenAmmoTemplate = ammoTpl,
ChosenUbglAmmoTemplate = ubglAmmoTpl,
WeaponMods = modPool,
WeaponTemplate = weaponItemTemplate,
WeaponTemplate = weaponItemTemplate
};
}
@@ -238,13 +231,13 @@ public class BotWeaponGenerator(
{
// Not found, add new slot to weapon
weaponWithModsList.Add(
new()
new Item
{
Id = _hashUtil.Generate(),
Template = ammoTemplate,
ParentId = weaponWithModsList[0].Id,
SlotId = slotId,
Upd = new() { StackObjectsCount = 1 },
Upd = new Upd { StackObjectsCount = 1 }
}
);
}
@@ -252,7 +245,7 @@ public class BotWeaponGenerator(
{
// Already exists, update values
existingItemWithSlot.Template = ammoTemplate;
existingItemWithSlot.Upd = new() { StackObjectsCount = 1 };
existingItemWithSlot.Upd = new Upd { StackObjectsCount = 1 };
}
}
}
@@ -272,7 +265,7 @@ public class BotWeaponGenerator(
{
return
[
new()
new Item
{
Id = _hashUtil.Generate(),
Template = weaponTemplate,
@@ -301,14 +294,12 @@ public class BotWeaponGenerator(
// TODO: Preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
Preset preset = null;
foreach (var (_, itemPreset) in _databaseService.GetGlobals().ItemPresets)
{
if (itemPreset.Items[0].Template == weaponTemplate)
{
preset = _cloner.Clone(itemPreset);
break;
}
}
if (preset is not null)
{
@@ -338,10 +329,7 @@ public class BotWeaponGenerator(
foreach (var mod in weaponItemList)
{
var modTemplate = _itemHelper.GetItem(mod.Template).Value;
if (!modTemplate.Properties.Slots?.Any() ?? false)
{
continue;
}
if (!modTemplate.Properties.Slots?.Any() ?? false) continue;
// Iterate over required slots in db item, check mod exists for that slot
foreach (var modSlotTemplate in modTemplate.Properties.Slots?.Where((slot) => slot.Required.GetValueOrDefault(false)) ?? [])
@@ -360,7 +348,7 @@ public class BotWeaponGenerator(
modSlot = modSlotTemplate.Name,
modName = modTemplate.Name,
slotId = mod.SlotId,
botRole = botRole,
botRole = botRole
}
)
);
@@ -394,6 +382,7 @@ public class BotWeaponGenerator(
return;
}
//var isInternalMag = magTemplate.Properties.ReloadMagType == ReloadMode.InternalMagazine;
var ammoTemplate = _itemHelper.GetItem(generatedWeaponResult.ChosenAmmoTemplate);
if (!ammoTemplate.Key)
@@ -406,10 +395,7 @@ public class BotWeaponGenerator(
}
// Has an UBGL
if (generatedWeaponResult.ChosenUbglAmmoTemplate is not null)
{
AddUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
}
if (generatedWeaponResult.ChosenUbglAmmoTemplate is not null) AddUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
var inventoryMagGenModel = new InventoryMagGen(
magWeights,
@@ -446,8 +432,8 @@ public class BotWeaponGenerator(
// Define min/max of how many grenades bot will have
GenerationData ubglMinMax = new()
{
Weights = new() { { 1, 1 }, { 2, 1 } },
Whitelist = new(),
Weights = new Dictionary<double, double> { { 1, 1 }, { 2, 1 } },
Whitelist = new Dictionary<string, double>()
};
// get ammo template from db
@@ -482,12 +468,12 @@ public class BotWeaponGenerator(
{
var id = _hashUtil.Generate();
_botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
new() { EquipmentSlots.SecuredContainer },
new List<EquipmentSlots> { EquipmentSlots.SecuredContainer },
id,
ammoTemplate,
new()
new List<Item>
{
new() { Id = id, Template = ammoTemplate, Upd = new() { StackObjectsCount = stackSize } }
new() { Id = id, Template = ammoTemplate, Upd = new Upd { StackObjectsCount = stackSize } }
},
inventory
);
@@ -508,14 +494,10 @@ public class BotWeaponGenerator(
{
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
// return default mag tpl
if (weaponTemplate.Properties.ReloadMode == ReloadMode.OnlyBarrel)
{
return _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
}
if (weaponTemplate.Properties.ReloadMode == ReloadMode.OnlyBarrel) return _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
if (!weaponTemplate.Properties.IsChamberLoad ?? false)
{
// Shouldn't happen
_logger.Warning(
_localisationService.GetText(
@@ -523,19 +505,16 @@ public class BotWeaponGenerator(
new
{
weaponId = weaponTemplate.Id,
botRole = botRole,
botRole = botRole
}
)
);
}
var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"[{botRole}] Unable to find magazine for weapon: {weaponTemplate.Id} {weaponTemplate.Name}, using mag template default: {defaultMagTplId}."
);
}
return defaultMagTplId;
}
@@ -554,8 +533,7 @@ public class BotWeaponGenerator(
var desiredCaliber = GetWeaponCaliber(weaponTemplate);
if (!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon) || cartridgePoolForWeapon?.Keys.Count == 0)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
_localisationService.GetText(
"bot-no_caliber_data_for_weapon_falling_back_to_default",
@@ -563,11 +541,10 @@ public class BotWeaponGenerator(
{
weaponId = weaponTemplate.Id,
weaponName = weaponTemplate.Name,
defaultAmmo = weaponTemplate.Properties.DefAmmo,
defaultAmmo = weaponTemplate.Properties.DefAmmo
}
)
);
}
// Immediately returns, default ammo is guaranteed to be compatible
return weaponTemplate.Properties.DefAmmo;
@@ -576,26 +553,18 @@ public class BotWeaponGenerator(
// Get cartridges the weapons first chamber allow
var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(weaponTemplate);
if (compatibleCartridgesInTemplate is null)
{
// No chamber data found in weapon, send default
return weaponTemplate.Properties.DefAmmo;
}
// Inner join the weapons allowed + passed in cartridge pool to get compatible cartridges
Dictionary<string, double> compatibleCartridges = new();
foreach (var cartridge in cartridgePoolForWeapon)
{
if (compatibleCartridgesInTemplate.Contains(cartridge.Key))
{
compatibleCartridges[cartridge.Key] = cartridgePoolForWeapon[cartridge.Key];
}
}
if (!compatibleCartridges.Any())
{
// No compatible cartridges, use default
return weaponTemplate.Properties.DefAmmo;
}
return _weightedRandomHelper.GetWeightedValue<string>(compatibleCartridges);
}
@@ -608,10 +577,7 @@ public class BotWeaponGenerator(
protected List<string>? GetCompatibleCartridgesFromWeaponTemplate(TemplateItem weaponTemplate)
{
var cartridges = weaponTemplate.Properties?.Chambers.FirstOrDefault()?.Props?.Filters?[0].Filter;
if (cartridges is not null)
{
return cartridges;
}
if (cartridges is not null) return cartridges;
// Fallback to the magazine if possible, e.g. for revolvers
// Grab the magazines template
var firstMagazine = weaponTemplate.Properties.Slots.FirstOrDefault(slot => slot.Name == "mod_magazine");
@@ -621,11 +587,9 @@ public class BotWeaponGenerator(
// Get the first slots array of cartridges
cartridges = magProperties.Slots.FirstOrDefault()?.Props.Filters?[0].Filter;
if (cartridges is null)
{
// Normal magazines
// None found, try the cartridges array
cartridges = magProperties.Cartridges.FirstOrDefault()?.Props.Filters[0].Filter;
}
return cartridges;
}
@@ -637,28 +601,20 @@ public class BotWeaponGenerator(
/// <returns>Caliber as string</returns>
protected string? GetWeaponCaliber(TemplateItem weaponTemplate)
{
if (weaponTemplate.Properties.Caliber is not null)
{
return weaponTemplate.Properties.Caliber;
}
if (weaponTemplate.Properties.Caliber is not null) return weaponTemplate.Properties.Caliber;
if (weaponTemplate.Properties.AmmoCaliber is not null)
{
// 9x18pmm has a typo, should be Caliber9x18PM
return weaponTemplate.Properties.AmmoCaliber == "Caliber9x18PMM"
? "Caliber9x18PM"
: weaponTemplate.Properties.AmmoCaliber;
}
if (weaponTemplate.Properties.LinkedWeapon is not null)
{
var ammoInChamber = _itemHelper.GetItem(
weaponTemplate.Properties.Chambers[0].Props.Filters[0].Filter[0]
);
if (!ammoInChamber.Key)
{
return null;
}
if (!ammoInChamber.Key) return null;
return ammoInChamber.Value.Properties.Caliber;
}
@@ -689,13 +645,9 @@ public class BotWeaponGenerator(
// Exchange of the camora ammo is not necessary we could also just check for stackSize > 0 here
// and remove the else
if (_botWeaponGeneratorHelper.MagazineIsCylinderRelated(parentItem.Name))
{
FillCamorasWithAmmo(weaponMods, magazine.Id, cartridgeTemplate);
}
else
{
AddOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTemplate, magazineTemplate);
}
}
/// <summary>
@@ -707,13 +659,13 @@ public class BotWeaponGenerator(
protected void FillUbgl(List<Item> weaponMods, Item ubglMod, string ubglAmmoTpl)
{
weaponMods.Add(
new()
new Item
{
Id = _hashUtil.Generate(),
Template = ubglAmmoTpl,
ParentId = ubglMod.Id,
SlotId = "patron_in_weapon",
Upd = new() { StackObjectsCount = 1 },
Upd = new Upd { StackObjectsCount = 1 }
}
);
}
@@ -731,10 +683,8 @@ public class BotWeaponGenerator(
(m) => m.ParentId == magazine.Id && m.SlotId == "cartridges"
);
if (magazineCartridgeChildItem is not null)
{
// Delete the existing cartridge object and create fresh below
weaponWithMods.Remove(magazineCartridgeChildItem);
}
// Create array with just magazine
List<Item> magazineWithCartridges = [magazine];
@@ -764,13 +714,9 @@ public class BotWeaponGenerator(
{
camora.Template = ammoTpl;
if (camora.Upd is not null)
{
camora.Upd.StackObjectsCount = 1;
}
else
{
camora.Upd = new() { StackObjectsCount = 1 };
}
camora.Upd = new Upd { StackObjectsCount = 1 };
}
}
}
@@ -40,47 +40,29 @@ public class FenceBaseAssortGenerator(
foreach (var rootItemDb in itemHelper.GetItems().Where(IsValidFenceItem))
{
// Skip blacklisted items
if (itemFilterService.IsItemBlacklisted(rootItemDb.Id))
{
continue;
}
if (itemFilterService.IsItemBlacklisted(rootItemDb.Id)) continue;
// Skip reward item blacklist
if (itemFilterService.IsItemRewardBlacklisted(rootItemDb.Id))
{
continue;
}
if (itemFilterService.IsItemRewardBlacklisted(rootItemDb.Id)) continue;
// Invalid
if (!itemHelper.IsValidItem(rootItemDb.Id))
{
continue;
}
if (!itemHelper.IsValidItem(rootItemDb.Id)) continue;
// Item base type blacklisted
if (traderConfig.Fence.Blacklist.Count > 0)
{
if (traderConfig.Fence.Blacklist.Contains(rootItemDb.Id) ||
itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist)
)
{
continue;
}
}
// Only allow rigs with no slots (carrier rigs)
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.VEST) &&
(rootItemDb.Properties?.Slots?.Count ?? 0) > 0
)
{
continue;
}
// Skip seasonal event items when not in seasonal event
if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(rootItemDb.Id))
{
continue;
}
if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(rootItemDb.Id)) continue;
// Create item object in array
var itemWithChildrenToAdd = new List<Item>
@@ -91,27 +73,19 @@ public class FenceBaseAssortGenerator(
Template = rootItemDb.Id,
ParentId = "hideout",
SlotId = "hideout",
Upd = new Upd { StackObjectsCount = 9999999 },
Upd = new Upd { StackObjectsCount = 9999999 }
}
};
// Ensure ammo is not above penetration limit value
if (itemHelper.IsOfBaseclasses(rootItemDb.Id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO]))
{
if (IsAmmoAbovePenetrationLimit(rootItemDb))
{
continue;
}
}
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
{
// Only add cartridges to box if box has no children
if (itemWithChildrenToAdd.Count == 1)
{
itemHelper.AddCartridgesToAmmoBox(itemWithChildrenToAdd, rootItemDb);
}
}
// Ensure IDs are unique
itemHelper.RemapRootItemId(itemWithChildrenToAdd);
@@ -143,10 +117,7 @@ public class FenceBaseAssortGenerator(
foreach (var defaultPreset in defaultPresets)
{
// Skip presets we've already added
if (baseFenceAssort.Items.Any((item) => item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id))
{
continue;
}
if (baseFenceAssort.Items.Any((item) => item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id)) continue;
// Construct preset + mods
var itemAndChildren = itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items));
@@ -165,7 +136,7 @@ public class FenceBaseAssortGenerator(
{
StackObjectsCount = 1,
SptPresetId =
defaultPreset.Id, // Store preset id here so we can check it later to prevent preset dupes
defaultPreset.Id // Store preset id here so we can check it later to prevent preset dupes
};
// Updated root item, exit loop
@@ -181,14 +152,14 @@ public class FenceBaseAssortGenerator(
var itemQualityModifier = itemHelper.GetItemQualityModifierForItems(itemAndChildren);
// Multiply weapon+mods rouble price by quality modifier
baseFenceAssort.BarterScheme[itemAndChildren[0].Id] = new()
baseFenceAssort.BarterScheme[itemAndChildren[0].Id] = new List<List<BarterScheme>>
{
new()
{
new BarterScheme
{
Template = Money.ROUBLES,
Count = Math.Round(price * itemQualityModifier),
Count = Math.Round(price * itemQualityModifier)
}
}
};
@@ -238,10 +209,7 @@ public class FenceBaseAssortGenerator(
}
// Plain old ammo, get its pen property
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO))
{
return rootItemDb.Properties.PenetrationPower;
}
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO)) return rootItemDb.Properties.PenetrationPower;
// Not an ammobox or ammo
return null;
@@ -256,26 +224,20 @@ public class FenceBaseAssortGenerator(
{
// Armor has no mods, make no additions
var hasMods = itemDbDetails.Properties.Slots.Count > 0;
if (!hasMods)
{
return;
}
if (!hasMods) return;
// Check for and add required soft inserts to armors
var requiredSlots = itemDbDetails.Properties.Slots.Where((slot) => slot.Required ?? false).ToList();
var hasRequiredSlots = requiredSlots.Count > 0;
if (hasRequiredSlots)
{
foreach (var requiredSlot in requiredSlots)
{
var modItemDbDetails = itemHelper.GetItem(requiredSlot.Props.Filters[0].Plate).Value;
var plateTpl =
requiredSlot.Props.Filters[0].Plate; // `Plate` property appears to be the 'default' item for slot
if (string.IsNullOrEmpty(plateTpl))
{
// Some bsg plate properties are empty, skip mod
continue;
}
var mod = new Item
{
@@ -283,33 +245,29 @@ public class FenceBaseAssortGenerator(
Template = plateTpl,
ParentId = armor[0].Id,
SlotId = requiredSlot.Name,
Upd = new()
Upd = new Upd
{
Repairable = new()
Repairable = new UpdRepairable
{
Durability = modItemDbDetails.Properties.MaxDurability,
MaxDurability = modItemDbDetails.Properties.MaxDurability,
},
},
MaxDurability = modItemDbDetails.Properties.MaxDurability
}
}
};
armor.Add(mod);
}
}
// Check for and add plate items
var plateSlots = itemDbDetails.Properties.Slots.Where((slot) => itemHelper.IsRemovablePlateSlot(slot.Name))
.ToList();
if (plateSlots.Count > 0)
{
foreach (var plateSlot in plateSlots)
{
var plateTpl = plateSlot.Props.Filters[0].Plate;
if (string.IsNullOrEmpty(plateTpl))
{
// Bsg data lacks a default plate, skip adding mod
continue;
}
var modItemDbDetails = itemHelper.GetItem(plateTpl).Value;
armor.Add(
@@ -319,18 +277,17 @@ public class FenceBaseAssortGenerator(
Template = plateSlot.Props.Filters[0].Plate, // `Plate` property appears to be the 'default' item for slot
ParentId = armor[0].Id,
SlotId = plateSlot.Name,
Upd = new()
Upd = new Upd
{
Repairable = new()
Repairable = new UpdRepairable
{
Durability = modItemDbDetails.Properties.MaxDurability,
MaxDurability = modItemDbDetails.Properties.MaxDurability,
},
},
MaxDurability = modItemDbDetails.Properties.MaxDurability
}
}
}
);
}
}
}
/**
@@ -51,43 +51,35 @@ public class LocationLootGenerator(
var staticWeaponsOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticWeapons);
if (staticWeaponsOnMapClone is null)
{
_logger.Error(
_localisationService.GetText("location-unable_to_find_static_weapon_for_map", locationBase.Name)
);
}
// Add mounted weapons to output loot
result.AddRange(staticWeaponsOnMapClone);
var allStaticContainersOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticContainers);
if (allStaticContainersOnMapClone is null)
{
_logger.Error(
_localisationService.GetText("location-unable_to_find_static_container_for_map", locationBase.Name)
);
}
// Containers that MUST be added to map (e.g. quest containers)
var staticForcedOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticForced);
if (staticForcedOnMapClone is null)
{
_logger.Error(
_localisationService.GetText(
"location-unable_to_find_forced_static_data_for_map",
locationBase.Name
)
);
}
// Remove christmas items from loot data
if (!_seasonalEventService.ChristmasEventEnabled())
{
allStaticContainersOnMapClone = allStaticContainersOnMapClone.Where(
item => !_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id)
)
.ToList();
}
var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(allStaticContainersOnMapClone);
@@ -114,10 +106,7 @@ public class LocationLootGenerator(
staticLootItemCount += containerWithLoot.Template.Items.Count;
}
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Added {guaranteedContainers.Count} guaranteed containers");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Added {guaranteedContainers.Count} guaranteed containers");
// Randomisation is turned off globally or just turned off for this map
if (
@@ -127,12 +116,10 @@ public class LocationLootGenerator(
)
)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Container randomisation disabled, Adding {staticRandomisableContainersOnMap.Count} containers to {locationBase.Name}"
);
}
foreach (var container in staticRandomisableContainersOnMap)
{
var containerWithLoot = AddLootToContainer(
@@ -166,17 +153,11 @@ public class LocationLootGenerator(
foreach (var (key, data) in mapping)
{
// Count chosen was 0, skip
if (data.ChosenCount == 0)
{
continue;
}
if (data.ChosenCount == 0) continue;
if (data.ContainerIdsWithProbability.Count == 0)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping");
continue;
}
@@ -189,18 +170,13 @@ public class LocationLootGenerator(
data.ContainerIdsWithProbability = new Dictionary<string, double>();
foreach (var containerId in containerIdsCopy)
if (_randomUtil.GetChance100(containerIdsCopy[containerId.Key] * 100))
{
data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[containerId.Key];
}
// Set desired count to size of array (we want all containers chosen)
data.ChosenCount = data.ContainerIdsWithProbability.Count;
// EDGE CASE: chosen container count could be 0
if (data.ChosenCount == 0)
{
continue;
}
if (data.ChosenCount == 0) continue;
}
// Pass possible containers into function to choose some
@@ -213,12 +189,10 @@ public class LocationLootGenerator(
);
if (containerObject is null)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Container: {chosenContainerId} not found in staticRandomisableContainersOnMap, this is bad"
);
}
continue;
}
@@ -296,12 +270,10 @@ public class LocationLootGenerator(
var containerIds = containerData.ContainerIdsWithProbability.Keys.ToList();
if (containerData.ChosenCount > containerIds.Count)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Group: {groupId} wants {containerData.ChosenCount} containers but pool only has {containerIds.Count}, add what's available"
);
}
return containerIds;
}
@@ -331,9 +303,7 @@ public class LocationLootGenerator(
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group
var mapping = new Dictionary<string, ContainerGroupCount>();
foreach (var groupKvP in staticContainerGroupData.ContainersGroups)
{
if (staticContainerGroupData.ContainersGroups.TryGetValue(groupKvP.Key, out var groupData))
{
mapping[groupKvP.Key] = new ContainerGroupCount
{
ContainerIdsWithProbability = new Dictionary<string, double>(),
@@ -348,8 +318,6 @@ public class LocationLootGenerator(
)
)
};
}
}
// Add an empty group for containers without a group id but still have a < 100% chance to spawn
// Likely bad BSG data, will be fixed...eventually, example of the groupids: `NEED_TO_BE_FIXED1`,`NEED_TO_BE_FIXED_SE02`, `NEED_TO_BE_FIXED_NW_01`
@@ -373,21 +341,22 @@ public class LocationLootGenerator(
if (container.Probability >= 1)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Container {container.Template.Id} with group: {groupData.GroupId} had 100 % chance to spawn was picked as random container, skipping"
);
}
continue;
}
mapping.TryAdd(groupData.GroupId, new ContainerGroupCount
{
ChosenCount = 0d,
ContainerIdsWithProbability = new Dictionary<string, double>()
});
mapping.TryAdd(
groupData.GroupId,
new ContainerGroupCount
{
ChosenCount = 0d,
ContainerIdsWithProbability = new Dictionary<string, double>()
}
);
mapping[groupData.GroupId].ContainerIdsWithProbability.TryAdd(container.Template.Id, container.Probability.Value);
}
@@ -447,10 +416,7 @@ public class LocationLootGenerator(
foreach (var tplToAdd in tplsToAddToContainer)
{
var chosenItemWithChildren = CreateStaticLootItem(tplToAdd, staticAmmoDist, parentId);
if (chosenItemWithChildren is null)
{
continue;
}
if (chosenItemWithChildren is null) continue;
var items = _locationConfig.TplsToStripChildItemsFrom.Contains(tplToAdd)
? [chosenItemWithChildren.Items[0]] // Strip children from parent
@@ -464,17 +430,15 @@ public class LocationLootGenerator(
if (!result.Success.GetValueOrDefault(false))
{
if (failedToFitCount > _locationConfig.FitLootIntoContainerAttempts)
{
// x attempts to fit an item, container is probably full, stop trying to add more
break;
}
// Can't fit item, skip
failedToFitCount++;
continue;
}
_containerHelper.FillContainerMapWithItem(
containerMap,
result.X.Value,
@@ -485,9 +449,9 @@ public class LocationLootGenerator(
);
var rotation = result.Rotation.GetValueOrDefault(false) ? 1 : 0;
items[0].SlotId = "main";
items[0].Location = new ItemLocation{ X = result.X, Y = result.Y, R = rotation };
items[0].Location = new ItemLocation { X = result.X, Y = result.Y, R = rotation };
// Add loot to container before returning
containerClone.Template.Items.AddRange(items);
@@ -517,7 +481,7 @@ public class LocationLootGenerator(
};
}
// Multi-mod-item, use helper to get size of item + attached mods
var result = _inventoryHelper.GetItemSize(rootItem.Template, rootItem.Id, items);
return new ItemSize
@@ -525,7 +489,6 @@ public class LocationLootGenerator(
Width = result[0],
Height = result[1]
};
}
/// <summary>
@@ -614,16 +577,11 @@ public class LocationLootGenerator(
foreach (var icd in itemContainerDistribution)
{
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(icd.Tpl))
{
// Skip seasonal event items if they're not enabled
continue;
}
// Ensure no blacklisted lootable items are in pool
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl))
{
continue;
}
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl)) continue;
itemDistribution.Add(new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null));
}
@@ -697,18 +655,12 @@ public class LocationLootGenerator(
// Point is blacklisted, skip
if (blacklistedSpawnpoints?.Contains(spawnpoint.Template.Id) ?? false)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Ignoring loose loot location: {spawnpoint.Template.Id}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Ignoring loose loot location: {spawnpoint.Template.Id}");
continue;
}
// We've handled IsAlwaysSpawn above, so skip them
if (spawnpoint.Template.IsAlwaysSpawn ?? false)
{
continue;
}
if (spawnpoint.Template.IsAlwaysSpawn ?? false) continue;
// 100%, add it to guaranteed
if (spawnpoint.Probability == 1)
@@ -728,13 +680,9 @@ public class LocationLootGenerator(
var randomSpawnpointCount = desiredSpawnpointCount - chosenSpawnpoints.Count;
// Only draw random spawn points if needed
if (randomSpawnpointCount > 0 && spawnpointArray.Count > 0)
{
// Add randomly chosen spawn points
foreach (var si in spawnpointArray.Draw((int)randomSpawnpointCount, false))
{
chosenSpawnpoints.Add(spawnpointArray.Data(si));
}
}
// Filter out duplicate locationIds // prob can be done better
chosenSpawnpoints = chosenSpawnpoints.GroupBy(spawnpoint => spawnpoint.LocationId).Select(group => group.First()).ToList();
@@ -742,9 +690,7 @@ public class LocationLootGenerator(
// Do we have enough items in pool to fulfill requirement
var tooManySpawnPointsRequested = desiredSpawnpointCount - chosenSpawnpoints.Count > 0;
if (tooManySpawnPointsRequested)
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
_localisationService.GetText(
"location-spawn_point_count_requested_vs_found",
@@ -752,12 +698,10 @@ public class LocationLootGenerator(
{
requested = desiredSpawnpointCount + guaranteedLoosePoints.Count,
found = chosenSpawnpoints.Count,
mapName = locationName,
mapName = locationName
}
)
);
}
}
// Iterate over spawnpoints
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
@@ -782,22 +726,18 @@ public class LocationLootGenerator(
// Ensure no seasonal items are in pool if not in-season
if (!seasonalEventActive)
{
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(
(item) => !seasonalItemTplBlacklist.Contains(item.Template)
)
.ToList();
}
// Spawn point has no items after filtering, skip
if (spawnPoint.Template.Items is null || spawnPoint.Template.Items.Count == 0)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
_localisationService.GetText("location-spawnpoint_missing_items", spawnPoint.Template.Id)
);
}
continue;
}
@@ -809,10 +749,7 @@ public class LocationLootGenerator(
var itemArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner);
foreach (var itemDist in spawnPoint.ItemDistribution)
{
if (!validItemIds.Contains(itemDist.ComposedKey.Key))
{
continue;
}
if (!validItemIds.Contains(itemDist.ComposedKey.Key)) continue;
itemArray.Add(new ProbabilityObject<string, double?>(itemDist.ComposedKey.Key, itemDist.RelativeProbability ?? 0, null));
}
@@ -858,7 +795,6 @@ public class LocationLootGenerator(
{
var lootToForceSingleAmountOnMap = _locationConfig.ForcedLootSingleSpawnById.GetValueOrDefault(locationName);
if (lootToForceSingleAmountOnMap is not null)
{
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
foreach (var itemTpl in lootToForceSingleAmountOnMap)
{
@@ -868,20 +804,16 @@ public class LocationLootGenerator(
);
if (items is null || !items.Any())
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug($"Unable to adjust loot item {itemTpl} as it does not exist inside {locationName} forced loot.");
}
continue;
}
// Create probability array of all spawn positions for this spawn id
var spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
foreach (var si in items)
{
// use locationId as template.Id is the same across all items
spawnpointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si));
}
// Choose 1 out of all found spawn positions for spawn id and add to loot array
foreach (var spawnPointLocationId in spawnpointArray.Draw(1, false))
@@ -906,7 +838,6 @@ public class LocationLootGenerator(
lootLocationTemplates.Add(lootItem);
}
}
}
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
@@ -917,16 +848,10 @@ public class LocationLootGenerator(
var firstLootItemTpl = forcedLootLocation.Template.Items.FirstOrDefault().Template;
// Skip spawn positions processed already
if (lootToForceSingleAmountOnMap?.Contains(firstLootItemTpl) ?? false)
{
continue;
}
if (lootToForceSingleAmountOnMap?.Contains(firstLootItemTpl) ?? false) continue;
// Skip adding seasonal items when seasonal event is not active
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(firstLootItemTpl))
{
continue;
}
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(firstLootItemTpl)) continue;
var locationTemplateToAdd = forcedLootLocation.Template;
var createItemResult = CreateDynamicLootItem(
@@ -949,12 +874,10 @@ public class LocationLootGenerator(
}
else
{
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"Attempted to add a forced loot location with Id: {locationTemplateToAdd.Id} to map {locationName} that already has that id in use, skipping"
);
}
}
}
}
@@ -963,39 +886,43 @@ public class LocationLootGenerator(
{
var chosenItem = items.FirstOrDefault((item) => item.Id == chosenComposedKey);
var chosenTpl = chosenItem?.Template;
if (chosenTpl is null) {
throw new Exception($"Item for tpl {chosenComposedKey} was not found in the spawn point");
}
if (chosenTpl is null) throw new Exception($"Item for tpl {chosenComposedKey} was not found in the spawn point");
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
if (itemTemplate is null) {
_logger.Error($"Item tpl: {chosenTpl} cannot be found in database");
}
if (itemTemplate is null) _logger.Error($"Item tpl: {chosenTpl} cannot be found in database");
// Item array to return
List<Item> itemWithMods = [];
// Money/Ammo - don't rely on items in spawnPoint.template.Items so we can randomise it ourselves
if (_itemHelper.IsOfBaseclasses(chosenTpl, [BaseClasses.MONEY, BaseClasses.AMMO])) {
if (_itemHelper.IsOfBaseclasses(chosenTpl, [BaseClasses.MONEY, BaseClasses.AMMO]))
{
var stackCount =
itemTemplate.Properties.StackMaxSize == 1
? 1
: _randomUtil.GetInt((int)itemTemplate.Properties.StackMinRandom, (int)itemTemplate.Properties.StackMaxRandom);
itemWithMods.Add(new Item {
Id = _hashUtil.Generate(),
Template = chosenTpl,
Upd = new Upd { StackObjectsCount = stackCount }
});
} else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) {
itemWithMods.Add(
new Item
{
Id = _hashUtil.Generate(),
Template = chosenTpl,
Upd = new Upd { StackObjectsCount = stackCount }
}
);
}
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
{
// Fill with cartridges
List<Item> ammoBoxItem = [ new Item { Id = _hashUtil.Generate(), Template = chosenTpl }];
List<Item> ammoBoxItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
_itemHelper.AddCartridgesToAmmoBox(ammoBoxItem, itemTemplate);
itemWithMods.AddRange(ammoBoxItem);
} else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MAGAZINE)) {
}
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
{
// Create array with just magazine
List<Item> magazineItem = [new Item { Id = _hashUtil.Generate(), Template = chosenTpl }];
List<Item> magazineItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent)) {
if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent))
// Add randomised amount of cartridges
_itemHelper.FillMagazineWithRandomCartridge(
magazineItem,
@@ -1004,10 +931,11 @@ public class LocationLootGenerator(
null,
_locationConfig.MinFillLooseMagazinePercent / 100
);
}
itemWithMods.AddRange(magazineItem);
} else {
}
else
{
// Also used by armors to get child mods
// Get item + children and add into array we return
var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, chosenItem.Id);
@@ -1015,10 +943,9 @@ public class LocationLootGenerator(
// Ensure all IDs are unique
itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren));
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template)) {
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template))
// Strip children from parent before adding
itemWithChildren = [itemWithChildren[0]];
}
itemWithMods.AddRange(itemWithChildren);
}
@@ -1033,8 +960,9 @@ public class LocationLootGenerator(
{
return _locationConfig.LooseLootMultiplier[location];
}
protected double getStaticLootMultiplerForLocation(string location) {
protected double getStaticLootMultiplerForLocation(string location)
{
return _locationConfig.StaticLootMultiplier[location];
}
@@ -1055,14 +983,11 @@ public class LocationLootGenerator(
var width = itemTemplate.Properties.Width;
var height = itemTemplate.Properties.Height;
List<Item> items = [new Item { Id = _hashUtil.Generate(), Template = chosenTpl }];
List<Item> items = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
var rootItem = items.FirstOrDefault();
// Use passed in parentId as override for new item
if (!string.IsNullOrEmpty(parentId))
{
rootItem.ParentId = parentId;
}
if (!string.IsNullOrEmpty(parentId)) rootItem.ParentId = parentId;
if (
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY) ||
@@ -1072,7 +997,7 @@ public class LocationLootGenerator(
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
var stackCount = itemTemplate.Properties.StackMaxSize == 1
? 1
: _randomUtil.GetInt((int)(itemTemplate.Properties.StackMinRandom), (int)(itemTemplate.Properties.StackMaxRandom));
: _randomUtil.GetInt((int)itemTemplate.Properties.StackMinRandom, (int)itemTemplate.Properties.StackMaxRandom);
rootItem.Upd = new Upd { StackObjectsCount = stackCount };
}
@@ -1100,7 +1025,7 @@ public class LocationLootGenerator(
tpl = chosenTpl,
defaultId = defaultPreset.Id,
defaultName = defaultPreset.Name,
parentId,
parentId
}
)
);
@@ -1111,10 +1036,7 @@ public class LocationLootGenerator(
else
{
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no chidren to reparent
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"createStaticLootItem() No preset found for weapon: {chosenTpl}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"createStaticLootItem() No preset found for weapon: {chosenTpl}");
}
rootItem = items[0];
@@ -1126,7 +1048,7 @@ public class LocationLootGenerator(
new
{
tpl = chosenTpl,
parentId,
parentId
}
)
);
@@ -1136,10 +1058,7 @@ public class LocationLootGenerator(
try
{
if (children?.Count > 0)
{
items = _itemHelper.ReparentItemAndChildren(rootItem, children);
}
if (children?.Count > 0) items = _itemHelper.ReparentItemAndChildren(rootItem, children);
}
catch (Exception e)
{
@@ -1149,7 +1068,7 @@ public class LocationLootGenerator(
new
{
tpl = chosenTpl,
parentId = parentId,
parentId = parentId
}
)
);
@@ -1230,13 +1149,11 @@ public class LocationLootGenerator(
{
// We make base item above, at start of function, no need to do it here
if ((itemTemplate.Properties.Slots?.Count ?? 0) > 0)
{
items = _itemHelper.AddChildSlotItems(
items,
itemTemplate,
_locationConfig.EquipmentLootSettings.ModSpawnChancePercent
);
}
}
}
+178 -169
View File
@@ -28,9 +28,8 @@ public class LootGenerator(
WeightedRandomHelper _weightedRandomHelper,
RagfairLinkedItemService _ragfairLinkedItemService,
ICloner _cloner
)
)
{
/// <summary>
/// Generate a list of items based on configuration options parameter
/// </summary>
@@ -44,24 +43,33 @@ public class LootGenerator(
// Handle sealed weapon containers
var sealedWeaponCrateCount = _randomUtil.GetDouble(
options.WeaponCrateCount.Min.Value,
options.WeaponCrateCount.Max.Value);
if (sealedWeaponCrateCount > 0) {
options.WeaponCrateCount.Max.Value
);
if (sealedWeaponCrateCount > 0)
{
// Get list of all sealed containers from db - they're all the same, just for flavor
var itemsDb = _itemHelper.GetItems();
var sealedWeaponContainerPool = (itemsDb).Where((item) =>
item.Name.Contains("event_container_airdrop"));
var sealedWeaponContainerPool = itemsDb.Where(
(item) =>
item.Name.Contains("event_container_airdrop")
);
for (var index = 0; index < sealedWeaponCrateCount; index++) {
for (var index = 0; index < sealedWeaponCrateCount; index++)
{
// Choose one at random + add to results array
var chosenSealedContainer = _randomUtil.GetArrayValue(sealedWeaponContainerPool);
result.Add( new Item{
Id = _hashUtil.Generate(),
Template = chosenSealedContainer.Id,
Upd = new Upd{
StackObjectsCount = 1,
SpawnedInSession = true
},
});
result.Add(
new Item
{
Id = _hashUtil.Generate(),
Template = chosenSealedContainer.Id,
Upd = new Upd
{
StackObjectsCount = 1,
SpawnedInSession = true
}
}
);
}
}
@@ -70,17 +78,17 @@ public class LootGenerator(
options.ItemBlacklist,
options.ItemTypeWhitelist,
options.UseRewardItemBlacklist.GetValueOrDefault(false),
options.AllowBossItems.GetValueOrDefault(false));
options.AllowBossItems.GetValueOrDefault(false)
);
// Pool has items we could add as loot, proceed
if (rewardPoolResults.ItemPool.Count > 0) {
if (rewardPoolResults.ItemPool.Count > 0)
{
var randomisedItemCount = _randomUtil.GetDouble(options.ItemCount.Min.Value, options.ItemCount.Max.Value);
for (var index = 0; index < randomisedItemCount; index++) {
if (!FindAndAddRandomItemToLoot(rewardPoolResults.ItemPool, itemTypeCounts, options, result)) {
for (var index = 0; index < randomisedItemCount; index++)
if (!FindAndAddRandomItemToLoot(rewardPoolResults.ItemPool, itemTypeCounts, options, result))
// Failed to add, reduce index so we get another attempt
index--;
}
}
}
var globalDefaultPresets = _presetHelper.GetDefaultPresets().Values;
@@ -88,52 +96,60 @@ public class LootGenerator(
// Filter default presets to just weapons
var randomisedWeaponPresetCount = _randomUtil.GetDouble(
options.WeaponPresetCount.Min.Value,
options.WeaponPresetCount.Max.Value);
if (randomisedWeaponPresetCount > 0) {
var weaponDefaultPresets = globalDefaultPresets.Where((preset) =>
_itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON)).ToList();
options.WeaponPresetCount.Max.Value
);
if (randomisedWeaponPresetCount > 0)
{
var weaponDefaultPresets = globalDefaultPresets.Where(
(preset) =>
_itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON)
)
.ToList();
if (weaponDefaultPresets.Any()) {
for (var index = 0; index < randomisedWeaponPresetCount; index++) {
if (weaponDefaultPresets.Any())
for (var index = 0; index < randomisedWeaponPresetCount; index++)
if (
!FindAndAddRandomPresetToLoot(
weaponDefaultPresets,
itemTypeCounts,
rewardPoolResults.Blacklist,
result)
) {
!FindAndAddRandomPresetToLoot(
weaponDefaultPresets,
itemTypeCounts,
rewardPoolResults.Blacklist,
result
)
)
// Failed to add, reduce index so we get another attempt
index--;
}
}
}
}
// Filter default presets to just armors and then filter again by protection level
var randomisedArmorPresetCount = _randomUtil.GetDouble(
options.ArmorPresetCount.Min.Value,
options.ArmorPresetCount.Max.Value);
if (randomisedArmorPresetCount > 0) {
var armorDefaultPresets = globalDefaultPresets.Where((preset) =>
_itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia));
var levelFilteredArmorPresets = armorDefaultPresets.Where((armor) =>
IsArmorOfDesiredProtectionLevel(armor, options)).ToList();
options.ArmorPresetCount.Max.Value
);
if (randomisedArmorPresetCount > 0)
{
var armorDefaultPresets = globalDefaultPresets.Where(
(preset) =>
_itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia)
);
var levelFilteredArmorPresets = armorDefaultPresets.Where(
(armor) =>
IsArmorOfDesiredProtectionLevel(armor, options)
)
.ToList();
// Add some armors to rewards
if (levelFilteredArmorPresets.Any()) {
for (var index = 0; index < randomisedArmorPresetCount; index++) {
if (levelFilteredArmorPresets.Any())
for (var index = 0; index < randomisedArmorPresetCount; index++)
if (
!FindAndAddRandomPresetToLoot(
levelFilteredArmorPresets,
itemTypeCounts,
rewardPoolResults.Blacklist,
result)
) {
!FindAndAddRandomPresetToLoot(
levelFilteredArmorPresets,
itemTypeCounts,
rewardPoolResults.Blacklist,
result
)
)
// Failed to add, reduce index so we get another attempt
index--;
}
}
}
}
return result;
@@ -151,18 +167,21 @@ public class LootGenerator(
var forcedItems = forcedLootDict;
foreach (var forcedItemKvP in forcedItems) {
foreach (var forcedItemKvP in forcedItems)
{
var details = forcedLootDict[forcedItemKvP.Key];
var randomisedItemCount = _randomUtil.GetDouble(details.Min.Value, details.Max.Value);
// Add forced loot item to result
var newLootItem = new Item{
var newLootItem = new Item
{
Id = _hashUtil.Generate(),
Template = forcedItemKvP.Key,
Upd = new Upd{
Upd = new Upd
{
StackObjectsCount = randomisedItemCount,
SpawnedInSession = true,
},
SpawnedInSession = true
}
};
var splitResults = _itemHelper.SplitStack(newLootItem);
@@ -195,7 +214,7 @@ public class LootGenerator(
// Get all items that match the blacklisted types and fold into item blacklist
var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist();
var itemsMatchingTypeBlacklist = (itemsDb)
var itemsMatchingTypeBlacklist = itemsDb
.Where((templateItem) => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist))
.Select((templateItem) => templateItem.Id);
@@ -207,20 +226,19 @@ public class LootGenerator(
}
if (!allowBossItems)
{
foreach (var bossItem in _itemFilterService.GetBossItems()) {
foreach (var bossItem in _itemFilterService.GetBossItems())
itemBlacklist.Add(bossItem);
}
}
var items = itemsDb.Where(
(item) =>
!itemBlacklist.Contains(item.Id) &&
(item) =>
!itemBlacklist.Contains(item.Id) &&
item.Type.ToLower() == "item" &&
!item.Properties.QuestItem.GetValueOrDefault(false) &&
itemTypeWhitelist.Contains(item.Parent)).ToList();
!item.Properties.QuestItem.GetValueOrDefault(false) &&
itemTypeWhitelist.Contains(item.Parent)
)
.ToList();
return new ItemRewardPoolResults{ ItemPool = items, Blacklist = itemBlacklist };
return new ItemRewardPoolResults { ItemPool = items, Blacklist = itemBlacklist };
}
public record ItemRewardPoolResults
@@ -238,12 +256,10 @@ public class LootGenerator(
protected bool IsArmorOfDesiredProtectionLevel(Preset armor, LootRequest options)
{
string[] relevantSlots = ["front_plate", "helmet_top", "soft_armor_front"];
foreach (var slotId in relevantSlots) {
foreach (var slotId in relevantSlots)
{
var armorItem = armor.Items.FirstOrDefault((item) => item?.SlotId?.ToLower() == slotId);
if (armorItem is null)
{
continue;
}
if (armorItem is null) continue;
var armorDetails = _itemHelper.GetItem(armorItem.Template).Value;
var armorClass = armorDetails.Properties.ArmorClass;
@@ -262,9 +278,7 @@ public class LootGenerator(
private Dictionary<string, ItemLimit> InitItemLimitCounter(Dictionary<string, double> limits)
{
var itemTypeCounts = new Dictionary<string, ItemLimit>();
foreach (var itemTypeId in limits) {
itemTypeCounts[itemTypeId.Key] = new ItemLimit() { Current = 0, Max = limits[itemTypeId.Key] };
}
foreach (var itemTypeId in limits) itemTypeCounts[itemTypeId.Key] = new ItemLimit() { Current = 0, Max = limits[itemTypeId.Key] };
return itemTypeCounts;
}
@@ -284,36 +298,31 @@ public class LootGenerator(
var randomItem = _randomUtil.GetArrayValue(items);
var itemLimitCount = itemTypeCounts.TryGetValue(randomItem.Parent, out var randomItemLimitCount);
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max) {
return false;
}
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max) return false;
// Skip armors as they need to come from presets
if (_itemHelper.ArmorItemCanHoldMods(randomItem.Id)) {
return false;
}
if (_itemHelper.ArmorItemCanHoldMods(randomItem.Id)) return false;
var newLootItem = new Item {
var newLootItem = new Item
{
Id = _hashUtil.Generate(),
Template = randomItem.Id,
Upd = new Upd {
Upd = new Upd
{
StackObjectsCount = 1,
SpawnedInSession = true,
},
SpawnedInSession = true
}
};
// Special case - handle items that need a stackcount > 1
if (randomItem.Properties.StackMaxSize > 1) {
newLootItem.Upd.StackObjectsCount = GetRandomisedStackCount(randomItem, options);
}
if (randomItem.Properties.StackMaxSize > 1) newLootItem.Upd.StackObjectsCount = GetRandomisedStackCount(randomItem, options);
newLootItem.Template = randomItem.Id;
result.Add(newLootItem);
if (randomItemLimitCount is not null) {
if (randomItemLimitCount is not null)
// Increment item count as it's in limit array
randomItemLimitCount.Current++;
}
// Item added okay
return true;
@@ -330,7 +339,8 @@ public class LootGenerator(
var min = item.Properties.StackMinRandom;
var max = item.Properties.StackMaxSize;
if (options.ItemStackLimits.TryGetValue(item.Id, out var itemLimits)) {
if (options.ItemStackLimits.TryGetValue(item.Id, out var itemLimits))
{
min = itemLimits.Min;
max = (int?)itemLimits.Max;
}
@@ -353,40 +363,36 @@ public class LootGenerator(
{
// Choose random preset and get details from item db using encyclopedia value (encyclopedia === tplId)
var chosenPreset = _randomUtil.GetArrayValue(presetPool);
if (chosenPreset is null ) {
if (chosenPreset is null)
{
_logger.Warning("Unable to find random preset in given presets, skipping");
return false;
}
// No `_encyclopedia` property, not possible to reliably get root item tpl
if (chosenPreset.Encyclopedia is null) {
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Preset with id: {chosenPreset?.Id} lacks encyclopedia property, skipping");
}
if (chosenPreset.Encyclopedia is null)
{
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Preset with id: {chosenPreset?.Id} lacks encyclopedia property, skipping");
return false;
}
// Get preset root item db details via its `_encyclopedia` property
var itemDbDetails = _itemHelper.GetItem(chosenPreset.Encyclopedia);
if (!itemDbDetails.Key) {
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping");
}
if (!itemDbDetails.Key)
{
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping");
return false;
}
// Skip preset if root item is blacklisted
if (itemBlacklist.Contains(chosenPreset.Items[0].Template)) {
return false;
}
if (itemBlacklist.Contains(chosenPreset.Items[0].Template)) return false;
// Some custom mod items lack a parent property
if (itemDbDetails.Value.Parent is null) {
if (itemDbDetails.Value.Parent is null)
{
_logger.Error(_localisationService.GetText("loot-item_missing_parentid", itemDbDetails.Value?.Name));
return false;
@@ -394,21 +400,16 @@ public class LootGenerator(
// Check chosen preset hasn't exceeded spawn limit
var hasItemLimitCount = itemTypeCounts.TryGetValue(itemDbDetails.Value.Parent, out var itemLimitCount);
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max) {
return false;
}
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max) return false;
var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(chosenPreset.Items));
_itemHelper.RemapRootItemId(presetAndMods);
// Add chosen preset tpl to result array
foreach (var item in presetAndMods) {
result.Add(item);
}
foreach (var item in presetAndMods) result.Add(item);
if (itemLimitCount is not null) {
if (itemLimitCount is not null)
// Increment item count as item has been chosen and its inside itemLimitCount dictionary
itemLimitCount.Current++;
}
// Item added okay
return true;
@@ -430,7 +431,8 @@ public class LootGenerator(
// Get itemDb details of weapon
var weaponDetailsDb = _itemHelper.GetItem(chosenWeaponTpl);
if (!weaponDetailsDb.Key) {
if (!weaponDetailsDb.Key)
{
_logger.Error(
_localisationService.GetText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl)
);
@@ -444,7 +446,8 @@ public class LootGenerator(
: _randomUtil.GetArrayValue(_presetHelper.GetPresets(chosenWeaponTpl));
// No default preset found for weapon, choose a random one
if (chosenWeaponPreset is null) {
if (chosenWeaponPreset is null)
{
_logger.Warning(
_localisationService.GetText("loot-default_preset_not_found_using_random", chosenWeaponTpl)
);
@@ -460,11 +463,12 @@ public class LootGenerator(
// Get a random collection of weapon mods related to chosen weawpon and add them to result array
var linkedItemsToWeapon = _ragfairLinkedItemService.GetLinkedDbItems(chosenWeaponTpl);
itemsToReturn.AddRange(GetSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset)
itemsToReturn.AddRange(
GetSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset)
);
// Handle non-weapon mod reward types
itemsToReturn.AddRange((GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value)));
itemsToReturn.AddRange(GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value));
return itemsToReturn;
}
@@ -480,35 +484,39 @@ public class LootGenerator(
{
List<List<Item>> rewards = [];
foreach (var (rewardKey,settings) in containerSettings.RewardTypeLimits) {
foreach (var (rewardKey, settings) in containerSettings.RewardTypeLimits)
{
var rewardCount = _randomUtil.GetDouble(settings.Min.Value, settings.Max.Value);
if (rewardCount == 0) {
continue;
}
if (rewardCount == 0) continue;
// Edge case - ammo boxes
if (rewardKey == BaseClasses.AMMO_BOX) {
if (rewardKey == BaseClasses.AMMO_BOX)
{
// Get ammoboxes from db
var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select((tpl) => {
var itemDetails = _itemHelper.GetItem(tpl);
return itemDetails.Value;
});
var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select(
(tpl) =>
{
var itemDetails = _itemHelper.GetItem(tpl);
return itemDetails.Value;
}
);
// Need to find boxes that matches weapons caliber
var weaponCaliber = weaponDetailsDb.Properties.AmmoCaliber;
var ammoBoxesMatchingCaliber = ammoBoxesDetails.Where((x) =>
x.Properties.AmmoCaliber == weaponCaliber);
if (!ammoBoxesMatchingCaliber.Any()) {
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"No ammo box with caliber {weaponCaliber} found, skipping");
}
var ammoBoxesMatchingCaliber = ammoBoxesDetails.Where(
(x) =>
x.Properties.AmmoCaliber == weaponCaliber
);
if (!ammoBoxesMatchingCaliber.Any())
{
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"No ammo box with caliber {weaponCaliber} found, skipping");
continue;
}
for (var index = 0; index < rewardCount; index++) {
for (var index = 0; index < rewardCount; index++)
{
var chosenAmmoBox = _randomUtil.GetArrayValue(ammoBoxesMatchingCaliber);
var ammoBoxReward = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenAmmoBox.Id } };
_itemHelper.AddCartridgesToAmmoBox(ammoBoxReward, chosenAmmoBox);
@@ -519,25 +527,25 @@ public class LootGenerator(
}
// Get all items of the desired type + not quest items + not globally blacklisted
var rewardItemPool = _databaseService.GetItems().Values.Where(
(item) =>
item.Parent == rewardKey &&
item.Type.ToLower() == "item" &&
_itemFilterService.IsItemBlacklisted(item.Id) &&
!(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id)) &&
item.Properties.QuestItem is null
);
var rewardItemPool = _databaseService.GetItems()
.Values.Where(
(item) =>
item.Parent == rewardKey &&
item.Type.ToLower() == "item" &&
_itemFilterService.IsItemBlacklisted(item.Id) &&
!(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id)) &&
item.Properties.QuestItem is null
);
if (rewardItemPool.Count() == 0) {
if(_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"No items with base type of {rewardKey} found, skipping");
}
if (rewardItemPool.Count() == 0)
{
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"No items with base type of {rewardKey} found, skipping");
continue;
}
for (var index = 0; index < rewardCount; index++) {
for (var index = 0; index < rewardCount; index++)
{
// Choose a random item from pool
var chosenRewardItem = _randomUtil.GetArrayValue(rewardItemPool);
var rewardItem = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenRewardItem.Id } };
@@ -560,31 +568,30 @@ public class LootGenerator(
Preset chosenWeaponPreset)
{
List<List<Item>> modRewards = [];
foreach (var (rewardKey,settings) in containerSettings.WeaponModRewardLimits) {
foreach (var (rewardKey, settings) in containerSettings.WeaponModRewardLimits)
{
var rewardCount = _randomUtil.GetDouble(settings.Min.Value, settings.Max.Value);
// Nothing to add, skip reward type
if (rewardCount == 0) {
continue;
}
if (rewardCount == 0) continue;
// Get items that fulfil reward type criteria from items that fit on gun
var relatedItems = linkedItemsToWeapon?.Where(
(item) => item?.Parent == rewardKey && !_itemFilterService.IsItemBlacklisted(item.Id)
);
if (relatedItems is null || relatedItems.Count() == 0) {
if(_logger.IsLogEnabled(LogLevel.Debug))
{
if (relatedItems is null || relatedItems.Count() == 0)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
_logger.Debug(
$"No items found to fulfil reward type: {rewardKey} for weapon: {chosenWeaponPreset.Name}, skipping type"
);
}
continue;
}
// Find a random item of the desired type and add as reward
for (var index = 0; index < rewardCount; index++) {
for (var index = 0; index < rewardCount; index++)
{
var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList());
var reward = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id } };
@@ -605,11 +612,13 @@ public class LootGenerator(
List<List<Item>> itemsToReturn = [];
// Get random items and add to newItemRequest
for (var index = 0; index < rewardContainerDetails.RewardCount; index++) {
for (var index = 0; index < rewardContainerDetails.RewardCount; index++)
{
// Pick random reward from pool, add to request object
var chosenRewardItemTpl = PickRewardItem(rewardContainerDetails);
if (_presetHelper.HasPreset(chosenRewardItemTpl)) {
if (_presetHelper.HasPreset(chosenRewardItemTpl))
{
var preset = _presetHelper.GetDefaultPreset(chosenRewardItemTpl);
// Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory
@@ -621,7 +630,7 @@ public class LootGenerator(
continue;
}
List<Item> rewardItem = [new Item { Id = _hashUtil.Generate(), Template = chosenRewardItemTpl }];
List<Item> rewardItem = [new() { Id = _hashUtil.Generate(), Template = chosenRewardItemTpl }];
itemsToReturn.Add(rewardItem);
}
@@ -635,14 +644,14 @@ public class LootGenerator(
/// <returns>Single tpl</returns>
protected string PickRewardItem(RewardDetails rewardContainerDetails)
{
if (rewardContainerDetails.RewardTplPool is not null && rewardContainerDetails.RewardTplPool.Count > 0) {
if (rewardContainerDetails.RewardTplPool is not null && rewardContainerDetails.RewardTplPool.Count > 0)
return _weightedRandomHelper.GetWeightedValue(rewardContainerDetails.RewardTplPool);
}
return _randomUtil.GetArrayValue(
GetItemRewardPool([], rewardContainerDetails.RewardTypePool, true, true).ItemPool.Select(
(item) => item.Id
)
GetItemRewardPool([], rewardContainerDetails.RewardTypePool, true, true)
.ItemPool.Select(
(item) => item.Id
)
);
}
}
+15 -35
View File
@@ -35,7 +35,7 @@ public class PMCLootGenerator
SeasonalEventService seasonalEventService,
WeightedRandomHelper weightedRandomHelper,
ConfigServer configServer
)
)
{
_logger = logger;
_databaseService = databaseService;
@@ -78,7 +78,6 @@ public class PMCLootGenerator
);
foreach (var (tpl, template) in itemsToAdd)
{
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
@@ -90,15 +89,12 @@ public class PMCLootGenerator
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_pocketLootPool[tpl] = price ?? 0;
}
}
var highestPrice = _pocketLootPool.Max(price => price.Value);
foreach (var (key, _) in _pocketLootPool)
{
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_pocketLootPool[key] = Math.Round((1 / _pocketLootPool[key]) * highestPrice);
}
_pocketLootPool[key] = Math.Round(1 / _pocketLootPool[key] * highestPrice);
_weightedRandomHelper.ReduceWeightValues(_pocketLootPool);
}
@@ -109,22 +105,10 @@ public class PMCLootGenerator
private HashSet<string> GetLootBlacklist()
{
var blacklist = new HashSet<string>();
foreach (var blacklistedItem in _pmcConfig.PocketLoot.Blacklist)
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _pmcConfig.GlobalLootBlacklist)
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _itemFilterService.GetBlacklistedItems())
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _seasonalEventService.GetInactiveSeasonalEventItems())
{
blacklist.Add(blacklistedItem);
}
foreach (var blacklistedItem in _pmcConfig.PocketLoot.Blacklist) blacklist.Add(blacklistedItem);
foreach (var blacklistedItem in _pmcConfig.GlobalLootBlacklist) blacklist.Add(blacklistedItem);
foreach (var blacklistedItem in _itemFilterService.GetBlacklistedItems()) blacklist.Add(blacklistedItem);
foreach (var blacklistedItem in _seasonalEventService.GetInactiveSeasonalEventItems()) blacklist.Add(blacklistedItem);
return blacklist;
}
@@ -154,10 +138,10 @@ public class PMCLootGenerator
_itemHelper.IsValidItem(item.Value.Id) &&
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent) &&
ItemFitsInto2By2Slot(item.Value));
ItemFitsInto2By2Slot(item.Value)
);
foreach (var (tpl, template) in itemsToAdd)
{
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
@@ -169,15 +153,12 @@ public class PMCLootGenerator
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_vestLootPool[tpl] = price ?? 0;
}
}
var highestPrice = _vestLootPool.Max(price => price.Value);
foreach (var (key, _) in _vestLootPool)
{
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_vestLootPool[key] = Math.Round((1 / _vestLootPool[key]) * highestPrice);
}
_vestLootPool[key] = Math.Round(1 / _vestLootPool[key] * highestPrice);
_weightedRandomHelper.ReduceWeightValues(_vestLootPool);
}
@@ -234,10 +215,11 @@ public class PMCLootGenerator
(item) =>
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
_itemHelper.IsValidItem(item.Value.Id) &&
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent));
!blacklist.Contains(item.Value.Id) &&
!blacklist.Contains(item.Value.Parent)
);
foreach (var (tpl, template) in itemsToAdd) {
foreach (var (tpl, template) in itemsToAdd)
// If pmc has price override, use that. Otherwise, use flea price
if (pmcPriceOverrides.ContainsKey(tpl))
{
@@ -249,14 +231,12 @@ public class PMCLootGenerator
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
_backpackLootPool[tpl] = price ?? 0;
}
}
var highestPrice = _backpackLootPool.Max(price => price.Value);
foreach (var (key, _) in _backpackLootPool) {
foreach (var (key, _) in _backpackLootPool)
// Invert price so cheapest has a larger weight
// Times by highest price so most expensive item has weight of 1
_backpackLootPool[key] = Math.Round((1 / _backpackLootPool[key]) * highestPrice);
}
_backpackLootPool[key] = Math.Round(1 / _backpackLootPool[key] * highestPrice);
_weightedRandomHelper.ReduceWeightValues(_backpackLootPool);
}
@@ -2,6 +2,7 @@ using SptCommon.Annotations;
using Core.Helpers;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Notes;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Models.Utils;
@@ -9,6 +10,7 @@ using Core.Servers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
using Core.Utils.Json;
using LogLevel = Core.Models.Spt.Logging.LogLevel;
@@ -53,14 +55,9 @@ public class PlayerScavGenerator(
// use karma level to get correct karmaSettings
if (!_playerScavConfig.KarmaLevel.TryGetValue(scavKarmaLevel.ToString(), out var playerScavKarmaSettings))
{
_logger.Error(_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel));
}
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Generated player scav loadout with karma level {scavKarmaLevel}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generated player scav loadout with karma level {scavKarmaLevel}");
// Edit baseBotNode values
var baseBotNode = ConstructBotBaseTemplate(playerScavKarmaSettings.BotTypeForLoot);
@@ -81,7 +78,7 @@ public class PlayerScavGenerator(
scavData.Savage = null;
scavData.Aid = pmcDataClone.Aid;
scavData.TradersInfo = pmcDataClone.TradersInfo;
scavData.Info.Settings = new();
scavData.Info.Settings = new BotInfoSettings();
scavData.Info.Bans = [];
scavData.Info.RegistrationDate = pmcDataClone.Info.RegistrationDate;
scavData.Info.GameVersion = pmcDataClone.Info.GameVersion;
@@ -98,10 +95,10 @@ public class PlayerScavGenerator(
scavData.Info.Level = GetScavLevel(existingScavDataClone);
scavData.Info.Experience = GetScavExperience(existingScavDataClone);
scavData.Quests = existingScavDataClone.Quests ?? [];
scavData.TaskConditionCounters = existingScavDataClone.TaskConditionCounters ?? new();
scavData.Notes = existingScavDataClone.Notes ?? new() { DataNotes = new() };
scavData.WishList = existingScavDataClone.WishList ?? new(new(), new());
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new();
scavData.TaskConditionCounters = existingScavDataClone.TaskConditionCounters ?? new Dictionary<string, 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>();
// Add additional items to player scav as loot
AddAdditionalLootToPlayerScavContainers(
@@ -168,12 +165,8 @@ public class PlayerScavGenerator(
);
if (result != ItemAddedResult.SUCCESS)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to add keycard to bot. Reason: {result.ToString()}");
}
}
}
}
@@ -232,17 +225,12 @@ public class PlayerScavGenerator(
foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment)
{
// Adjustment value zero, nothing to do
if (equipmentKvP.Value == 0)
{
continue;
}
if (equipmentKvP.Value == 0) continue;
// Try add new key with value
if (!baseBotNode.BotChances.EquipmentChances.TryAdd(equipmentKvP.Key, equipmentKvP.Value))
{
// Unable to add new, update existing
baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value;
}
}
// Adjust mod chance values
@@ -255,8 +243,9 @@ public class PlayerScavGenerator(
{
baseBotNode.BotChances.WeaponModsChances.TryAdd(modKvP.Key, 0);
baseBotNode.BotChances.WeaponModsChances[modKvP.Key] += value;
};
}
;
}
// Adjust item spawn quantity values
@@ -271,10 +260,7 @@ public class PlayerScavGenerator(
foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist)
{
baseBotNode.BotInventory.Equipment.TryGetValue(equipmentBlacklistKvP.Key, out var equipmentDict);
foreach (var itemToRemove in equipmentBlacklistKvP.Value)
{
equipmentDict.Remove(itemToRemove);
}
foreach (var itemToRemove in equipmentBlacklistKvP.Value) equipmentDict.Remove(itemToRemove);
}
}
@@ -288,10 +274,10 @@ public class PlayerScavGenerator(
protected Skills GetDefaultScavSkills()
{
return new()
return new Skills
{
Common = new(),
Mastering = new(),
Common = new List<BaseSkill>(),
Mastering = new List<BaseSkill>(),
Points = 0
};
}
@@ -337,14 +323,10 @@ public class PlayerScavGenerator(
var modifier = 1;
foreach (var bonus in pmcData.Bonuses)
{
if (bonus.Type == BonusType.ScavCooldownTimer)
{
// Value is negative, so add.
// Also note that for scav cooldown, multiple bonuses stack additively.
modifier += (int)(bonus?.Value ?? 1) / 100;
}
}
var fenceInfo = _fenceService.GetFenceInfo(pmcData);
modifier *= (int)(fenceInfo.SavageCooldownModifier ?? 1);
@@ -32,7 +32,7 @@ public class RagfairAssortGenerator(
BaseClasses.INVENTORY,
BaseClasses.STATIONARY_CONTAINER,
BaseClasses.POCKETS,
BaseClasses.BUILT_IN_INSERTS,
BaseClasses.BUILT_IN_INSERTS
];
/**
@@ -42,10 +42,7 @@ public class RagfairAssortGenerator(
*/
public List<List<Item>> GetAssortItems()
{
if (!AssortsAreGenerated())
{
generatedAssortItems = GenerateRagfairAssortItems();
}
if (!AssortsAreGenerated()) generatedAssortItems = GenerateRagfairAssortItems();
return generatedAssortItems;
}
@@ -95,10 +92,7 @@ public class RagfairAssortGenerator(
foreach (var item in dbItemsClone)
{
if (!itemHelper.IsValidItem(item.Id, ragfairItemInvalidBaseTypes))
{
continue;
}
if (!itemHelper.IsValidItem(item.Id, ragfairItemInvalidBaseTypes)) continue;
// Skip seasonal items when not in-season
if (
@@ -106,15 +100,11 @@ public class RagfairAssortGenerator(
!seasonalEventActive &&
seasonalItemTplBlacklist.Contains(item.Id)
)
{
continue;
}
if (processedArmorItems.Contains(item.Id))
{
// Already processed
continue;
}
var ragfairAssort = CreateRagfairAssortRootItem(
item.Id,
+174 -178
View File
@@ -50,7 +50,7 @@ public class RagfairOfferGenerator(
/** Internal counter to ensure each offer created has a unique value for its intId property */
protected int offerCounter = 0;
/**
* Create a flea offer and store it in the Ragfair server offers array
* @param userID Owner of the offer
@@ -97,21 +97,27 @@ public class RagfairOfferGenerator(
{
var isTrader = ragfairServerHelper.IsTrader(userID);
var offerRequirements = barterScheme.Select((barter) => {
var offerRequirement = new OfferRequirement{
Template = barter.Template,
Count = Math.Round((double) barter.Count, 2),
OnlyFunctional = barter.OnlyFunctional ?? false,
};
var offerRequirements = barterScheme.Select(
(barter) =>
{
var offerRequirement = new OfferRequirement
{
Template = barter.Template,
Count = Math.Round((double)barter.Count, 2),
OnlyFunctional = barter.OnlyFunctional ?? false
};
// Dogtags define level and side
if (barter.Level != null) {
offerRequirement.Level = barter.Level;
offerRequirement.Side = barter.Side;
}
// Dogtags define level and side
if (barter.Level != null)
{
offerRequirement.Level = barter.Level;
offerRequirement.Side = barter.Side;
}
return offerRequirement;
}).ToList();
return offerRequirement;
}
)
.ToList();
// Clone to avoid modifying original array
var itemsClone = cloner.Clone(items);
@@ -119,28 +125,28 @@ public class RagfairOfferGenerator(
// Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box)
// On offer refresh don't re-add cartridges to ammo box that already has cartridges
if (itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) && itemsClone.Count == 1) {
if (itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) && itemsClone.Count == 1)
itemHelper.AddCartridgesToAmmoBox(itemsClone, itemHelper.GetItem(items[0].Template).Value);
}
var roubleListingPrice = Math.Round((double) ConvertOfferRequirementsIntoRoubles(offerRequirements));
var roubleListingPrice = Math.Round((double)ConvertOfferRequirementsIntoRoubles(offerRequirements));
var singleItemListingPrice = isPackOffer ? roubleListingPrice / itemStackCount : roubleListingPrice;
var offer = new RagfairOffer {
Id= hashUtil.Generate(),
InternalId= offerCounter,
User= CreateUserDataForFleaOffer(userID, isTrader),
Root= items[0].Id,
Items= itemsClone,
ItemsCost= Math.Round((double) handbookHelper.GetTemplatePrice(items[0].Template)), // Handbook price
Requirements= offerRequirements,
RequirementsCost= Math.Round(singleItemListingPrice),
SummaryCost= roubleListingPrice,
StartTime= time,
EndTime= GetOfferEndTime(userID, time),
LoyaltyLevel= loyalLevel,
SellInOnePiece= isPackOffer,
Locked= false,
var offer = new RagfairOffer
{
Id = hashUtil.Generate(),
InternalId = offerCounter,
User = CreateUserDataForFleaOffer(userID, isTrader),
Root = items[0].Id,
Items = itemsClone,
ItemsCost = Math.Round((double)handbookHelper.GetTemplatePrice(items[0].Template)), // Handbook price
Requirements = offerRequirements,
RequirementsCost = Math.Round(singleItemListingPrice),
SummaryCost = roubleListingPrice,
StartTime = time,
EndTime = GetOfferEndTime(userID, time),
LoyaltyLevel = loyalLevel,
SellInOnePiece = isPackOffer,
Locked = false
};
offerCounter++;
@@ -157,40 +163,43 @@ public class RagfairOfferGenerator(
protected RagfairOfferUser CreateUserDataForFleaOffer(string userID, bool isTrader)
{
// Trader offer
if (isTrader) {
return new RagfairOfferUser(){
if (isTrader)
return new RagfairOfferUser()
{
Id = userID,
MemberType = MemberCategory.Trader
};
}
var isPlayerOffer = profileHelper.IsPlayer(userID);
if (isPlayerOffer) {
if (isPlayerOffer)
{
var playerProfile = profileHelper.GetPmcProfile(userID);
return new RagfairOfferUser {
Id= playerProfile.Id,
MemberType= playerProfile.Info.MemberCategory,
SelectedMemberCategory= playerProfile.Info.SelectedMemberCategory,
Nickname= playerProfile.Info.Nickname,
Rating= playerProfile.RagfairInfo.Rating ?? 0,
IsRatingGrowing= playerProfile.RagfairInfo.IsRatingGrowing,
Avatar= null,
Aid= playerProfile.Aid,
return new RagfairOfferUser
{
Id = playerProfile.Id,
MemberType = playerProfile.Info.MemberCategory,
SelectedMemberCategory = playerProfile.Info.SelectedMemberCategory,
Nickname = playerProfile.Info.Nickname,
Rating = playerProfile.RagfairInfo.Rating ?? 0,
IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing,
Avatar = null,
Aid = playerProfile.Aid
};
}
// Fake pmc offer
return new RagfairOfferUser(){
Id= userID,
MemberType= MemberCategory.Default,
Nickname= botHelper.GetPmcNicknameOfMaxLength(botConfig.BotNameLengthLimit),
Rating= randomUtil.GetDouble(
(double) ragfairConfig.Dynamic.Rating.Min,
(double) ragfairConfig.Dynamic.Rating.Max
return new RagfairOfferUser()
{
Id = userID,
MemberType = MemberCategory.Default,
Nickname = botHelper.GetPmcNicknameOfMaxLength(botConfig.BotNameLengthLimit),
Rating = randomUtil.GetDouble(
(double)ragfairConfig.Dynamic.Rating.Min,
(double)ragfairConfig.Dynamic.Rating.Max
),
IsRatingGrowing= randomUtil.GetBool(),
Avatar= null,
Aid= hashUtil.GenerateAccountId(),
IsRatingGrowing = randomUtil.GetBool(),
Avatar = null,
Aid = hashUtil.GenerateAccountId()
};
}
@@ -199,13 +208,13 @@ public class RagfairOfferGenerator(
* @param offerRequirements barter requirements for offer
* @returns rouble cost of offer
*/
protected int ConvertOfferRequirementsIntoRoubles(List<OfferRequirement> offerRequirements) {
protected int ConvertOfferRequirementsIntoRoubles(List<OfferRequirement> offerRequirements)
{
var roublePrice = 0;
foreach (var requirement in offerRequirements) {
roublePrice += (int) (paymentHelper.IsMoneyTpl(requirement.Template)
? Math.Round((double) CalculateRoublePrice((int) requirement.Count, requirement.Template))
foreach (var requirement in offerRequirements)
roublePrice += (int)(paymentHelper.IsMoneyTpl(requirement.Template)
? Math.Round((double)CalculateRoublePrice((int)requirement.Count, requirement.Template))
: ragfairPriceService.GetFleaPriceForItem(requirement.Template) * requirement.Count); // get flea price for barter offer items
}
return roublePrice;
}
@@ -218,9 +227,7 @@ public class RagfairOfferGenerator(
*/
protected string GetAvatarUrl(bool isTrader, string userId)
{
if (isTrader) {
return databaseService.GetTrader(userId).Base.Avatar;
}
if (isTrader) return databaseService.GetTrader(userId).Base.Avatar;
return "/files/trader/avatar/unknown.jpg";
}
@@ -233,9 +240,7 @@ public class RagfairOfferGenerator(
*/
protected int CalculateRoublePrice(int currencyCount, string currencyType)
{
if (currencyType == Money.ROUBLES) {
return currencyCount;
}
if (currencyType == Money.ROUBLES) return currencyCount;
return handbookHelper.InRUB(currencyCount, currencyType);
}
@@ -247,9 +252,7 @@ public class RagfairOfferGenerator(
*/
protected string GetTraderId(string userId)
{
if (profileHelper.IsPlayer(userId)) {
return saveServer.GetProfile(userId).CharacterData.PmcData.Id;
}
if (profileHelper.IsPlayer(userId)) return saveServer.GetProfile(userId).CharacterData.PmcData.Id;
return userId;
}
@@ -261,18 +264,16 @@ public class RagfairOfferGenerator(
*/
protected double? GetRating(string userId)
{
if (profileHelper.IsPlayer(userId)) {
if (profileHelper.IsPlayer(userId))
// Player offer
return saveServer.GetProfile(userId).CharacterData?.PmcData?.RagfairInfo?.Rating;
}
if (ragfairServerHelper.IsTrader(userId)) {
if (ragfairServerHelper.IsTrader(userId))
// Trader offer
return 1;
}
// Generated pmc offer
return randomUtil.GetDouble((double) ragfairConfig.Dynamic.Rating.Min, (double) ragfairConfig.Dynamic.Rating.Max);
return randomUtil.GetDouble((double)ragfairConfig.Dynamic.Rating.Min, (double)ragfairConfig.Dynamic.Rating.Max);
}
/**
@@ -283,15 +284,12 @@ public class RagfairOfferGenerator(
protected bool GetRatingGrowing(string userID)
{
if (profileHelper.IsPlayer(userID))
{
// player offer
return saveServer.GetProfile(userID).CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
}
if (ragfairServerHelper.IsTrader(userID)) {
if (ragfairServerHelper.IsTrader(userID))
// trader offer
return true;
}
// generated offer
// 50/50 growing/falling
@@ -306,19 +304,21 @@ public class RagfairOfferGenerator(
*/
protected long GetOfferEndTime(string userID, long time)
{
if (profileHelper.IsPlayer(userID)) {
if (profileHelper.IsPlayer(userID))
{
// Player offer = current time + offerDurationTimeInHour;
var offerDurationTimeHours = databaseService.GetGlobals().Configuration.RagFair.OfferDurationTimeInHour;
return (long) (timeUtil.GetTimeStamp() + Math.Round((double) offerDurationTimeHours * TimeUtil.OneHourAsSeconds));
return (long)(timeUtil.GetTimeStamp() + Math.Round((double)offerDurationTimeHours * TimeUtil.OneHourAsSeconds));
}
if (ragfairServerHelper.IsTrader(userID)) {
if (ragfairServerHelper.IsTrader(userID))
// Trader offer
return (long) databaseService.GetTrader(userID).Base.NextResupply;
}
return (long)databaseService.GetTrader(userID).Base.NextResupply;
// Generated fake-player offer
return (long) Math.Round((double) (time + randomUtil.GetInt((int) ragfairConfig.Dynamic.EndTimeSeconds.Min, (int) ragfairConfig.Dynamic.EndTimeSeconds.Max)));
return (long)Math.Round(
(double)(time + randomUtil.GetInt((int)ragfairConfig.Dynamic.EndTimeSeconds.Min, (int)ragfairConfig.Dynamic.EndTimeSeconds.Max))
);
}
/**
@@ -335,26 +335,18 @@ public class RagfairOfferGenerator(
? expiredOffers ?? []
: ragfairAssortGenerator.GetAssortItems();
stopwatch.Stop();
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts");
}
if (logger.IsLogEnabled(LogLevel.Debug)) logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts");
stopwatch.Restart();
var tasks = new List<Task>();
foreach (var assortItem in assortItemsToProcess)
{
tasks.Add(
Task.Factory.StartNew(
() => { CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic); }
)
);
}
Task.WaitAll(tasks.ToArray());
stopwatch.Stop();
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to CreateOffersFromAssort");
}
if (logger.IsLogEnabled(LogLevel.Debug)) logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to CreateOffersFromAssort");
}
/**
@@ -372,20 +364,17 @@ public class RagfairOfferGenerator(
var isPreset = presetHelper.IsPreset(assortItemWithChildren[0].Upd.SptPresetId);
// Only perform checks on newly generated items, skip expired items being refreshed
if (!(isExpiredOffer || ragfairServerHelper.IsItemValidRagfairItem(itemToSellDetails))) {
return;
}
if (!(isExpiredOffer || ragfairServerHelper.IsItemValidRagfairItem(itemToSellDetails))) return;
// Armor presets can hold plates above the allowed flea level, remove if necessary
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList) {
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList)
RemoveBannedPlatesFromPreset(assortItemWithChildren, ragfairConfig.Dynamic.Blacklist.ArmorPlate);
}
// Get number of offers to create
// Limit to 1 offer when processing expired - like-for-like replacement
var offerCount = isExpiredOffer
? 1
: randomUtil.GetInt((int) config.OfferItemCount.Min, (int) config.OfferItemCount.Max);
: randomUtil.GetInt((int)config.OfferItemCount.Min, (int)config.OfferItemCount.Max);
/* TODO: ???????
if (ProgramStatics.DEBUG && !ProgramStatics.COMPILED) {
@@ -393,7 +382,8 @@ public class RagfairOfferGenerator(
}
*/
for (var index = 0; index < offerCount; index++) {
for (var index = 0; index < offerCount; index++)
{
// Clone the item so we don't have shared references and generate new item IDs
var clonedAssort = cloner.Clone(assortItemWithChildren);
itemHelper.ReparentItemAndChildren(clonedAssort[0], clonedAssort);
@@ -417,26 +407,24 @@ public class RagfairOfferGenerator(
ArmorPlateBlacklistSettings plateSettings
)
{
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template)) {
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template))
// Cant hold armor inserts, skip
return false;
}
var plateSlots = presetWithChildren.Where((item) => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())). ToList();
if (plateSlots.Count == 0) {
var plateSlots = presetWithChildren.Where((item) => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())).ToList();
if (plateSlots.Count == 0)
// Has no plate slots e.g. "front_plate", exit
return false;
}
var removedPlate = false;
foreach (var plateSlot in plateSlots) {
foreach (var plateSlot in plateSlots)
{
var plateDetails = itemHelper.GetItem(plateSlot.Template).Value;
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower())) {
continue;
}
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower())) continue;
var plateArmorLevel = plateDetails.Properties.ArmorClass ?? 0;
if (plateArmorLevel > plateSettings.MaxProtectionLevel) {
if (plateArmorLevel > plateSettings.MaxProtectionLevel)
{
presetWithChildren.Splice(presetWithChildren.IndexOf(plateSlot), 1);
removedPlate = true;
}
@@ -492,15 +480,13 @@ public class RagfairOfferGenerator(
// Latest first, to ensure we don't move later items off by 1 each time we remove an item below it
var indexesToRemove = offerItemPlatesToRemove.Select(plateItem => itemWithChildren.IndexOf(plateItem))
.ToList();
foreach (var index in indexesToRemove.OrderByDescending(x => x))
{
itemWithChildren.RemoveAt(index);
}
foreach (var index in indexesToRemove.OrderByDescending(x => x)) itemWithChildren.RemoveAt(index);
}
}
List<BarterScheme> barterScheme;
if (isPackOffer) {
if (isPackOffer)
{
// Set pack size
var stackSize = randomUtil.GetInt(
ragfairConfig.Dynamic.Pack.ItemCountMin,
@@ -510,14 +496,16 @@ public class RagfairOfferGenerator(
// Don't randomise pack items
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer, stackSize);
} else if (isBarterOffer) {
}
else if (isBarterOffer)
{
// Apply randomised properties
RandomiseOfferItemUpdProperties(sellerId, itemWithChildren, itemToSellDetails);
barterScheme = CreateBarterBarterScheme(itemWithChildren, ragfairConfig.Dynamic.Barter);
if (ragfairConfig.Dynamic.Barter.MakeSingleStackOnly) {
itemWithChildren[0].Upd.StackObjectsCount = 1;
}
} else {
if (ragfairConfig.Dynamic.Barter.MakeSingleStackOnly) itemWithChildren[0].Upd.StackObjectsCount = 1;
}
else
{
// Apply randomised properties
RandomiseOfferItemUpdProperties(sellerId, itemWithChildren, itemToSellDetails);
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer);
@@ -547,7 +535,8 @@ public class RagfairOfferGenerator(
var assortsClone = cloner.Clone(trader.Assort);
// Trader assorts / assort items are missing
if (assortsClone?.Items?.Count is null or 0) {
if (assortsClone?.Items?.Count is null or 0)
{
logger.Error(
localisationService.GetText(
"ragfair-no_trader_assorts_cant_generate_flea_offers",
@@ -558,25 +547,25 @@ public class RagfairOfferGenerator(
}
var blacklist = ragfairConfig.Dynamic.Blacklist;
foreach (var item in assortsClone.Items) {
foreach (var item in assortsClone.Items)
{
// We only want to process 'base/root' items, no children
if (item.SlotId != "hideout") {
if (item.SlotId != "hideout")
// skip mod items
continue;
}
// Run blacklist check on trader offers
if (blacklist.TraderItems) {
if (blacklist.TraderItems)
{
var itemDetails = itemHelper.GetItem(item.Template);
if (!itemDetails.Key) {
if (!itemDetails.Key)
{
logger.Warning(localisationService.GetText("ragfair-tpl_not_a_valid_item", item.Template));
continue;
}
// Don't include items that BSG has blacklisted from flea
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)) {
continue;
}
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)) continue;
}
var isPreset = presetHelper.IsPreset(item.Id);
@@ -617,17 +606,16 @@ public class RagfairOfferGenerator(
// Add any missing properties to first item in array
AddMissingConditions(itemWithMods[0]);
if (!(profileHelper.IsPlayer(userID) || ragfairServerHelper.IsTrader(userID))) {
if (!(profileHelper.IsPlayer(userID) || ragfairServerHelper.IsTrader(userID)))
{
var parentId = GetDynamicConditionIdForTpl(itemDetails.Id);
if (string.IsNullOrEmpty(parentId)) {
if (string.IsNullOrEmpty(parentId))
// No condition details found, don't proceed with modifying item conditions
return;
}
// Roll random chance to randomise item condition
if (randomUtil.GetChance100(ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100)) {
if (randomUtil.GetChance100(ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100))
RandomiseItemCondition(parentId, itemWithMods, itemDetails);
}
}
}
@@ -640,11 +628,9 @@ public class RagfairOfferGenerator(
{
// Get keys from condition config dictionary
var configConditions = ragfairConfig.Dynamic.Condition.Keys;
foreach (var baseClass in configConditions) {
if (itemHelper.IsOfBaseclass(tpl, baseClass)) {
foreach (var baseClass in configConditions)
if (itemHelper.IsOfBaseclass(tpl, baseClass))
return baseClass;
}
}
return null;
}
@@ -664,21 +650,23 @@ public class RagfairOfferGenerator(
var rootItem = itemWithMods[0];
var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId];
var maxMultiplier = randomUtil.GetDouble((double) itemConditionValues.Max.Min, (double) itemConditionValues.Max.Min);
var maxMultiplier = randomUtil.GetDouble((double)itemConditionValues.Max.Min, (double)itemConditionValues.Max.Min);
var currentMultiplier = randomUtil.GetDouble(
(double) itemConditionValues.Current.Min,
(double) itemConditionValues.Current.Max
(double)itemConditionValues.Current.Min,
(double)itemConditionValues.Current.Max
);
// Randomise armor + plates + armor related things
if (itemHelper.ArmorItemCanHoldMods(rootItem.Template) ||
itemHelper.IsOfBaseclasses(rootItem.Template, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT])
) {
)
{
RandomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier);
// Add hits to visor
var visorMod = itemWithMods.FirstOrDefault((item) => item.ParentId == BaseClasses.ARMORED_EQUIPMENT && item.SlotId == "mod_equipment_000");
if (randomUtil.GetChance100(25) && visorMod != null) {
if (randomUtil.GetChance100(25) && visorMod != null)
{
itemHelper.AddUpdObjectToItem(visorMod);
visorMod.Upd.FaceShield = new UpdFaceShield() { Hits = randomUtil.GetInt(1, 3) };
@@ -688,26 +676,30 @@ public class RagfairOfferGenerator(
}
// Randomise Weapons
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON)) {
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON))
{
RandomiseWeaponDurability(itemWithMods[0], itemDetails, maxMultiplier, currentMultiplier);
return;
}
if (rootItem.Upd?.MedKit != null) {
if (rootItem.Upd?.MedKit != null)
{
// Randomize health
var hpResource = Math.Round((double)rootItem.Upd.MedKit.HpResource * maxMultiplier);
rootItem.Upd.MedKit.HpResource = hpResource == 0D ? 1D : hpResource;
return;
}
if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1) {
if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1)
{
// Randomize key uses
rootItem.Upd.Key.NumberOfUsages = (int?)Math.Round(itemDetails.Properties.MaximumNumberOfUsage.Value * (1 - maxMultiplier));
return;
}
if (rootItem.Upd?.FoodDrink != null) {
if (rootItem.Upd?.FoodDrink != null)
{
// randomize food/drink value
var hpPercent = Math.Round((double)itemDetails.Properties.MaxResource * maxMultiplier);
rootItem.Upd.FoodDrink.HpPercent = hpPercent == 0D ? 1D : hpPercent;
@@ -715,7 +707,8 @@ public class RagfairOfferGenerator(
return;
}
if (rootItem.Upd?.RepairKit != null) {
if (rootItem.Upd?.RepairKit != null)
{
// randomize repair kit (armor/weapon) uses
var resource = Math.Round((double)itemDetails.Properties.MaxRepairResource * maxMultiplier);
rootItem.Upd.RepairKit.Resource = resource == 0D ? 1D : resource;
@@ -723,9 +716,10 @@ public class RagfairOfferGenerator(
return;
}
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL)) {
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL))
{
var totalCapacity = itemDetails.Properties.MaxResource;
var remainingFuel = Math.Round((double) totalCapacity * maxMultiplier);
var remainingFuel = Math.Round((double)totalCapacity * maxMultiplier);
rootItem.Upd.Resource = new UpdResource()
{ UnitsConsumed = totalCapacity - remainingFuel, Value = remainingFuel };
}
@@ -748,7 +742,7 @@ public class RagfairOfferGenerator(
// Max
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability));
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double)lowestMaxDurability, (double)baseMaxDurability));
// Current
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
@@ -770,19 +764,22 @@ public class RagfairOfferGenerator(
double maxMultiplier
)
{
foreach (var armorItem in armorWithMods) {
foreach (var armorItem in armorWithMods)
{
var itemDbDetails = itemHelper.GetItem(armorItem.Template).Value;
if (itemDbDetails.Properties.ArmorClass > 1) {
if (itemDbDetails.Properties.ArmorClass > 1)
{
itemHelper.AddUpdObjectToItem(armorItem);
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability,(double) baseMaxDurability));
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double)lowestMaxDurability, (double)baseMaxDurability));
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability));
armorItem.Upd.Repairable = new UpdRepairable() {
armorItem.Upd.Repairable = new UpdRepairable()
{
Durability = chosenCurrentDurability == 0D ? 1D : chosenCurrentDurability, // Never var value become 0
MaxDurability = chosenMaxDurability
};
@@ -796,7 +793,8 @@ public class RagfairOfferGenerator(
* HpResource for medical items
* @param item item to add conditions to
*/
protected void AddMissingConditions(Item item) {
protected void AddMissingConditions(Item item)
{
var props = itemHelper.GetItem(item.Template).Value.Properties;
var isRepairable = props.Durability != null;
var isMedkit = props.MaxHpResource != null;
@@ -804,7 +802,8 @@ public class RagfairOfferGenerator(
var isConsumable = props.MaxResource > 1 && props.FoodUseTime != null;
var isRepairKit = props.MaxRepairResource != null;
if (isRepairable && props.Durability > 0) {
if (isRepairable && props.Durability > 0)
{
item.Upd.Repairable = new UpdRepairable()
{ Durability = props.Durability, MaxDurability = props.Durability };
@@ -818,22 +817,22 @@ public class RagfairOfferGenerator(
return;
}
if (isKey) {
item.Upd.Key = new UpdKey(){ NumberOfUsages = 0 };
if (isKey)
{
item.Upd.Key = new UpdKey() { NumberOfUsages = 0 };
return;
}
// Food/drink
if (isConsumable) {
if (isConsumable)
{
item.Upd.FoodDrink = new UpdFoodDrink() { HpPercent = props.MaxResource };
return;
}
if (isRepairKit) {
item.Upd.RepairKit = new UpdRepairKit() { Resource = props.MaxRepairResource };
}
if (isRepairKit) item.Upd.RepairKit = new UpdRepairKit() { Resource = props.MaxRepairResource };
}
/**
@@ -852,9 +851,7 @@ public class RagfairOfferGenerator(
);
// Dont make items under a designated rouble value into barter offers
if (priceOfOfferItem < barterConfig.MinRoubleCostToBecomeBarter) {
return CreateCurrencyBarterScheme(offerItems, false);
}
if (priceOfOfferItem < barterConfig.MinRoubleCostToBecomeBarter) return CreateCurrencyBarterScheme(offerItems, false);
// Get a randomised number of barter items to list offer for
var barterItemCount = randomUtil.GetInt(barterConfig.ItemCountMin, barterConfig.ItemCountMax);
@@ -863,23 +860,22 @@ public class RagfairOfferGenerator(
var desiredItemCostRouble = Math.Round(priceOfOfferItem / barterItemCount);
// Rouble amount to go above/below when looking for an item (Wiggle cost of item a little)
var offerCostVarianceRoubles = (desiredItemCostRouble * barterConfig.PriceRangeVariancePercent) / 100;
var offerCostVarianceRoubles = desiredItemCostRouble * barterConfig.PriceRangeVariancePercent / 100;
// Dict of items and their flea price (cached on first use)
var itemFleaPrices = GetFleaPricesAsArray();
// Filter possible barters to items that match the price range + not itself
var itemsInsidePriceBounds = itemFleaPrices.Where(
itemAndPrice =>
itemAndPrice.Price >= desiredItemCostRouble - offerCostVarianceRoubles &&
itemAndPrice.Price <= desiredItemCostRouble + offerCostVarianceRoubles &&
itemAndPrice.Tpl != offerItems[0].Template // Don't allow the item being sold to be chosen
).ToList();
itemAndPrice =>
itemAndPrice.Price >= desiredItemCostRouble - offerCostVarianceRoubles &&
itemAndPrice.Price <= desiredItemCostRouble + offerCostVarianceRoubles &&
itemAndPrice.Tpl != offerItems[0].Template // Don't allow the item being sold to be chosen
)
.ToList();
// No items on flea have a matching price, fall back to currency
if (itemsInsidePriceBounds.Count == 0) {
return CreateCurrencyBarterScheme(offerItems, false);
}
if (itemsInsidePriceBounds.Count == 0) return CreateCurrencyBarterScheme(offerItems, false);
// Choose random item from price-filtered flea items
var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds);
@@ -894,7 +890,8 @@ public class RagfairOfferGenerator(
protected List<TplWithFleaPrice> GetFleaPricesAsArray()
{
// Generate if needed
if (allowedFleaPriceItemsForBarter == null) {
if (allowedFleaPriceItemsForBarter == null)
{
var fleaPrices = databaseService.GetPrices();
// Only get prices for items that also exist in items.json
@@ -928,4 +925,3 @@ public class RagfairOfferGenerator(
return [new BarterScheme() { Count = price, Template = currency }];
}
}
@@ -101,7 +101,8 @@ public class RepeatableQuestGenerator(
var locationsConfig = repeatableConfig.Locations;
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(_mathUtil, _cloner, eliminationConfig.Targets);
var bodyPartsConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.BodyParts);
var weaponCategoryRequirementConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponCategoryRequirements);
var weaponCategoryRequirementConfig =
new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponCategoryRequirements);
var weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponRequirements);
// the difficulty of the quest varies in difficulty depending on the condition
@@ -178,9 +179,7 @@ public class RepeatableQuestGenerator(
.ToList();
if (questTypePool.Pool.Elimination.Targets.GetByJsonProp<TargetLocation>(targetKey).Locations.Count ==
0)
{
questTypePool.Pool.Elimination.Targets.Remove(targetKey);
}
}
else
{
@@ -243,10 +242,13 @@ public class RepeatableQuestGenerator(
if (eliminationConfig.DistanceProbability > rand.NextDouble() && isDistanceRequirementAllowed)
{
// Random distance with lower values more likely; simple distribution for starters...
distance = (int)Math.Floor(Math.Abs(rand.NextDouble() - rand.NextDouble()) *
(1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance) +
eliminationConfig.MinDistance ?? 0);
distance = (int)Math.Floor(
Math.Abs(rand.NextDouble() - rand.NextDouble()) *
(1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance) +
eliminationConfig.MinDistance ??
0
);
distance = (int)Math.Ceiling((decimal)(distance / 5)) * 5;
distanceDifficulty = (int)(maxDistDifficulty * distance / eliminationConfig.MaxDistance);
}
@@ -317,10 +319,7 @@ public class RepeatableQuestGenerator(
var quest = GenerateRepeatableTemplate("Elimination", traderId, repeatableConfig.Side, sessionId);
// ASSUMPTION: All fence quests are for scavs
if (traderId == Traders.FENCE)
{
quest.Side = "Scav";
}
if (traderId == Traders.FENCE) quest.Side = "Scav";
var availableForFinishCondition = quest.Conditions.AvailableForFinish[0];
availableForFinishCondition.Counter.Id = _hashUtil.Generate();
@@ -372,14 +371,10 @@ public class RepeatableQuestGenerator(
EliminationConfig eliminationConfig)
{
if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false))
{
return _randomUtil.RandInt(eliminationConfig.MinBossKills.Value, eliminationConfig.MaxBossKills + 1);
}
if (targetsConfig.Data(targetKey).IsPmc.GetValueOrDefault(false))
{
return _randomUtil.RandInt(eliminationConfig.MinPmcKills.Value, eliminationConfig.MaxPmcKills + 1);
}
return _randomUtil.RandInt(eliminationConfig.MinKills.Value, eliminationConfig.MaxKills + 1);
}
@@ -448,22 +443,14 @@ public class RepeatableQuestGenerator(
}
// Has specific body part hit condition
if (targetedBodyParts is not null)
{
killConditionProps.BodyPart = targetedBodyParts;
}
if (targetedBodyParts is not null) killConditionProps.BodyPart = targetedBodyParts;
// Don't allow distance + melee requirement
if (distance is not null && allowedWeaponCategory != "5b5f7a0886f77409407a7f96")
{
killConditionProps.Distance = new CounterConditionDistance { CompareMethod = ">=", Value = distance.Value };
}
// Has specific weapon requirement
if (allowedWeapon is not null)
{
killConditionProps.Weapon = [allowedWeapon];
}
if (allowedWeapon is not null) killConditionProps.Weapon = [allowedWeapon];
// Has specific weapon category requirement
if (allowedWeaponCategory?.Length > 0)
@@ -637,11 +624,9 @@ public class RepeatableQuestGenerator(
var x = (int)Math.Floor(roublesBudget / itemUnitPrice);
maxValue = Math.Min(maxValue, x);
if (maxValue > minValue)
{
// If it doesn't blow the budget we have for the request, draw a random amount of the selected
// Item type to be requested
value = _randomUtil.RandInt(minValue, maxValue + 1);
}
roublesBudget -= value * itemUnitPrice;
@@ -654,10 +639,7 @@ public class RepeatableQuestGenerator(
// Reduce the list possible items to fulfill the new budget constraint
itemSelection = itemSelection.Where(dbItem => _itemHelper.GetItemPrice(dbItem.Id) < roublesBudget)
.ToList();
if (!itemSelection.Any())
{
break;
}
if (!itemSelection.Any()) break;
}
else
{
@@ -694,15 +676,10 @@ public class RepeatableQuestGenerator(
_itemHelper.IsOfBaseclass(itemTpl, BaseClasses.WEAPON) ||
_itemHelper.IsOfBaseclass(itemTpl, BaseClasses.ARMOR)
)
{
minDurability = _randomUtil.GetArrayValue([60, 80]);
}
// By default all collected items must be FiR, except dog tags
if (_itemHelper.IsDogtag(itemTpl))
{
onlyFoundInRaid = false;
}
if (_itemHelper.IsDogtag(itemTpl)) onlyFoundInRaid = false;
return new QuestCondition
{
@@ -43,12 +43,12 @@ public class RepeatableQuestRewardGenerator(
* - Items
* - Trader Reputation
* - Skill level experience
*
*
* The reward is dependent on the player level as given by the wiki. The exact mapping of pmcLevel to
* experience / money / items / trader reputation can be defined in QuestConfig.js
*
*
* There's also a random variation of the reward the spread of which can be also defined in the config
*
*
* Additionally, a scaling factor w.r.t. quest difficulty going from 0.2...1 can be used
* @param pmcLevel Level of player reward is being generated for
* @param difficulty Reward scaling factor from 0.2 to 1
@@ -130,19 +130,14 @@ public class RepeatableQuestRewardGenerator(
var filteredRewardItemPool = inBudgetRewardItemPool.Where(
item => !rewardTplBlacklist.Contains(item.Id)
);
if (filteredRewardItemPool.Count() > 0)
{
inBudgetRewardItemPool = filteredRewardItemPool.ToList();
}
if (filteredRewardItemPool.Count() > 0) inBudgetRewardItemPool = filteredRewardItemPool.ToList();
}
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Generating: {repeatableConfig.Name} quest for: {traderId} with budget: {itemRewardBudget} totalling: {rewardParams.RewardNumItems} items"
);
}
if (inBudgetRewardItemPool.Count > 0)
{
var itemsToReward = GetRewardableItemsFromPoolWithinBudget(
@@ -177,10 +172,7 @@ public class RepeatableQuestRewardGenerator(
rewards.Success.Add(reward);
rewardIndex++;
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward");
}
// Chance of adding skill reward
@@ -200,10 +192,7 @@ public class RepeatableQuestRewardGenerator(
};
rewards.Success.Add(reward);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}");
}
return rewards;
@@ -223,10 +212,7 @@ public class RepeatableQuestRewardGenerator(
var reputationConfig = rewardScaling.Reputation;
var effectiveDifficulty = difficulty is null ? 1 : difficulty;
if (difficulty is null)
{
_logger.Warning(_localisationService.GetText("repeatable-difficulty_was_nan"));
}
if (difficulty is null) _logger.Warning(_localisationService.GetText("repeatable-difficulty_was_nan"));
return new QuestRewardValues
{
@@ -325,10 +311,7 @@ public class RepeatableQuestRewardGenerator(
// Get a random item
var chosenItemFromPool = exhausableItemPool.GetRandomValue();
if (!exhausableItemPool.HasValues())
{
break;
}
if (!exhausableItemPool.HasValues()) break;
// Handle edge case - ammo
if (_itemHelper.IsOfBaseclass(chosenItemFromPool.Id, BaseClasses.AMMO))
@@ -351,18 +334,13 @@ public class RepeatableQuestRewardGenerator(
// 25% chance to double, triple or quadruple reward stack
// (Only occurs when item is stackable and not weapon, armor or ammo)
if (CanIncreaseRewardItemStackSize(chosenItemFromPool, 70000, 25))
{
rewardItemStackCount = GetRandomisedRewardItemStackSizeByPrice(chosenItemFromPool);
}
itemsToReturn.Add(chosenItemFromPool, rewardItemStackCount);
var itemCost = _presetHelper.GetDefaultPresetOrItemPrice(chosenItemFromPool.Id);
var calculatedItemRewardBudget = itemRewardBudget - rewardItemStackCount * itemCost;
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}");
// If we still have budget narrow down possible items
if (calculatedItemRewardBudget > 0)
@@ -375,12 +353,8 @@ public class RepeatableQuestRewardGenerator(
);
if (!exhausableItemPool.HasValues())
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining");
}
}
}
// No budget for more items, end loop
@@ -453,10 +427,7 @@ public class RepeatableQuestRewardGenerator(
// Find the appropriate price tier and return a random stack size from its options
var tier = priceTiers.FirstOrDefault(tier => rewardItemPrice < tier.Item1);
if (tier is null)
{
return 4; // Default to 2 if no tier matches
}
if (tier is null) return 4; // Default to 2 if no tier matches
return _randomUtil.GetArrayValue(tier.Item2);
}
@@ -533,10 +504,7 @@ public class RepeatableQuestRewardGenerator(
while (defaultPresetPool.HasValues())
{
var randomPreset = defaultPresetPool.GetRandomValue();
if (randomPreset is null)
{
continue;
}
if (randomPreset is null) continue;
// Gather all tpls so we can get prices of them
var tpls = randomPreset.Items.Select(item => item.Template).ToList();
@@ -560,7 +528,7 @@ public class RepeatableQuestRewardGenerator(
/**
* Helper to create a reward item structured as required by the client
*
*
* @param {string} tpl ItemId of the rewarded item
* @param {integer} count Amount of items to give
* @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index
@@ -587,15 +555,9 @@ public class RepeatableQuestRewardGenerator(
// Get presets root item
var rootItem = preset.FirstOrDefault(item => item.Template == tpl);
if (rootItem is null)
{
_logger.Warning($"Root item of preset: {tpl} not found");
}
if (rootItem is null) _logger.Warning($"Root item of preset: {tpl} not found");
if (rootItem.Upd is not null)
{
rootItem.Upd.SpawnedInSession = foundInRaid;
}
if (rootItem.Upd is not null) rootItem.Upd.SpawnedInSession = foundInRaid;
questRewardItem.Items = _itemHelper.ReparentItemAndChildren(rootItem, preset);
questRewardItem.Target = rootItem.Id; // Target property and root items id must match
@@ -605,7 +567,7 @@ public class RepeatableQuestRewardGenerator(
/**
* Helper to create a reward item structured as required by the client
*
*
* @param {string} tpl ItemId of the rewarded item
* @param {integer} count Amount of items to give
* @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index
@@ -677,15 +639,9 @@ public class RepeatableQuestRewardGenerator(
itemTemplate =>
{
// Base "Item" item has no parent, ignore it
if (itemTemplate.Parent == "")
{
return false;
}
if (itemTemplate.Parent == "") return false;
if (seasonalItems.Contains(itemTemplate.Id))
{
return false;
}
if (seasonalItems.Contains(itemTemplate.Id)) return false;
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(
trader => trader.TraderId == traderId
@@ -711,10 +667,7 @@ public class RepeatableQuestRewardGenerator(
List<string>? itemBaseWhitelist = null)
{
// Return early if not valid item to give as reward
if (!_itemHelper.IsValidItem(tpl))
{
return false;
}
if (!_itemHelper.IsValidItem(tpl)) return false;
// Check item is not blacklisted
if (
@@ -723,27 +676,16 @@ public class RepeatableQuestRewardGenerator(
repeatableQuestConfig.RewardBlacklist.Contains(tpl) ||
_itemFilterService.IsItemBlacklisted(tpl)
)
{
return false;
}
// Item has blacklisted base types
if (_itemHelper.IsOfBaseclasses(tpl, repeatableQuestConfig.RewardBaseTypeBlacklist))
{
return false;
}
if (_itemHelper.IsOfBaseclasses(tpl, repeatableQuestConfig.RewardBaseTypeBlacklist)) return false;
// Skip boss items
if (_itemFilterService.IsBossItem(tpl))
{
return false;
}
if (_itemFilterService.IsBossItem(tpl)) return false;
// Trader has specific item base types they can give as rewards to player
if (itemBaseWhitelist is not null && !_itemHelper.IsOfBaseclasses(tpl, itemBaseWhitelist))
{
return false;
}
if (itemBaseWhitelist is not null && !_itemHelper.IsOfBaseclasses(tpl, itemBaseWhitelist)) return false;
return true;
}
@@ -93,26 +93,16 @@ public class ScavCaseRewardGenerator(
// Get an array of seasonal items that should not be shown right now as seasonal event is not active
var inactiveSeasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
if (!_dbItemsCache.Any())
{
_dbItemsCache = _databaseService.GetItems()
.Values.Where(
item =>
{
// Base "Item" item has no parent, ignore it
if (item.Parent == "")
{
return false;
}
if (item.Parent == "") return false;
if (item.Type == "Node")
{
return false;
}
if (item.Type == "Node") return false;
if (item.Properties.QuestItem ?? false)
{
return false;
}
if (item.Properties.QuestItem ?? false) return false;
// Skip item if item id is on blacklist
if (
@@ -120,98 +110,58 @@ public class ScavCaseRewardGenerator(
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
_itemFilterService.IsItemBlacklisted(item.Id)
)
{
return false;
}
// Globally reward-blacklisted
if (_itemFilterService.IsItemRewardBlacklisted(item.Id))
{
return false;
}
if (_itemFilterService.IsItemRewardBlacklisted(item.Id)) return false;
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
{
return false;
}
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id)) return false;
// Skip item if parent id is blacklisted
if (_itemHelper.IsOfBaseclasses(item.Id, _scavCaseConfig.RewardItemParentBlacklist))
{
return false;
}
if (_itemHelper.IsOfBaseclasses(item.Id, _scavCaseConfig.RewardItemParentBlacklist)) return false;
if (inactiveSeasonalItems.Contains(item.Id))
{
return false;
}
if (inactiveSeasonalItems.Contains(item.Id)) return false;
return true;
}
)
.ToList();
}
if (!_dbAmmoItemsCache.Any())
{
_dbAmmoItemsCache = _databaseService.GetItems()
.Values.Where(
item =>
{
// Base "Item" item has no parent, ignore it
if (item.Parent == "")
{
return false;
}
if (item.Parent == "") return false;
if (item.Type != "Item")
{
return false;
}
if (item.Type != "Item") return false;
// Not ammo, skip
if (!_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO))
{
return false;
}
if (!_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO)) return false;
// Skip item if item id is on blacklist
if (
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
_itemFilterService.IsItemBlacklisted(item.Id)
)
{
return false;
}
// Globally reward-blacklisted
if (_itemFilterService.IsItemRewardBlacklisted(item.Id))
{
return false;
}
if (_itemFilterService.IsItemRewardBlacklisted(item.Id)) return false;
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
{
return false;
}
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id)) return false;
// Skip seasonal items
if (inactiveSeasonalItems.Contains(item.Id))
{
return false;
}
if (inactiveSeasonalItems.Contains(item.Id)) return false;
// Skip ammo that doesn't stack as high as value in config
if (item.Properties.StackMaxSize < _scavCaseConfig.AmmoRewards.MinStackSize)
{
return false;
}
if (item.Properties.StackMaxSize < _scavCaseConfig.AmmoRewards.MinStackSize) return false;
return true;
}
)
.ToList();
}
}
/// <summary>
@@ -231,30 +181,22 @@ public class ScavCaseRewardGenerator(
var rewardWasAmmo = false;
var randomCount = _randomUtil.GetInt((int)itemFilters.MinCount, (int)itemFilters.MaxCount);
for (var i = 0; i < randomCount; i++)
{
if (RewardShouldBeMoney() && !rewardWasMoney)
{
// Only allow one reward to be money
result.Add(GetRandomMoney());
if (!_scavCaseConfig.AllowMultipleMoneyRewardsPerRarity)
{
rewardWasMoney = true;
}
if (!_scavCaseConfig.AllowMultipleMoneyRewardsPerRarity) rewardWasMoney = true;
}
else if (RewardShouldBeAmmo() && !rewardWasAmmo)
{
// Only allow one reward to be ammo
result.Add(GetRandomAmmo(rarity));
if (!_scavCaseConfig.AllowMultipleAmmoRewardsPerRarity)
{
rewardWasAmmo = true;
}
if (!_scavCaseConfig.AllowMultipleAmmoRewardsPerRarity) rewardWasAmmo = true;
}
else
{
result.Add(_randomUtil.GetArrayValue(items));
}
}
return result;
}
@@ -308,18 +250,13 @@ public class ScavCaseRewardGenerator(
handbookPrice >= _scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub[rarity].Min &&
handbookPrice <= _scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub[rarity].Max
)
{
return true;
}
return false;
}
);
if (!possibleAmmoPool.Any())
{
_logger.Warning("Unable to get a list of ammo that matches desired criteria for scav case reward");
}
if (!possibleAmmoPool.Any()) _logger.Warning("Unable to get a list of ammo that matches desired criteria for scav case reward");
// Get a random ammo and return it
return _randomUtil.GetArrayValue(possibleAmmoPool);
@@ -337,7 +274,7 @@ public class ScavCaseRewardGenerator(
List<List<Item>> result = [];
foreach (var rewardItemDb in rewardItems)
{
List<Item> resultItem = [new Item { Id = _hashUtil.Generate(), Template = rewardItemDb.Id, Upd = null }];
List<Item> resultItem = [new() { Id = _hashUtil.Generate(), Template = rewardItemDb.Id, Upd = null }];
var rootItem = resultItem.FirstOrDefault();
if (_itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.AMMO_BOX))
@@ -359,7 +296,7 @@ public class ScavCaseRewardGenerator(
}
// Ensure preset has unique ids and is cloned so we don't alter the preset data stored in memory
List<Item> presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(preset.Items));
var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(preset.Items));
_itemHelper.RemapRootItemId(presetAndMods);
resultItem = presetAndMods;
@@ -388,10 +325,7 @@ public class ScavCaseRewardGenerator(
item =>
{
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id);
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub)
{
return true;
}
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub) return true;
return false;
}
@@ -443,14 +377,11 @@ public class ScavCaseRewardGenerator(
{
var amountToGive = 1;
if (itemToCalculate.Parent == BaseClasses.AMMO)
{
amountToGive = _randomUtil.GetInt(
_scavCaseConfig.AmmoRewards.MinStackSize,
itemToCalculate.Properties.StackMaxSize ?? 0
);
}
else if (itemToCalculate.Parent == BaseClasses.MONEY)
{
amountToGive = itemToCalculate.Id switch
{
Money.ROUBLES => _randomUtil.GetInt(
@@ -471,7 +402,6 @@ public class ScavCaseRewardGenerator(
),
_ => amountToGive
};
}
return amountToGive;
}
@@ -26,17 +26,13 @@ public class BarrelInvetoryMagGen(
// Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot"
double? randomisedAmmoStackSize = null;
if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1)
{
// doesnt stack
randomisedAmmoStackSize = _randomUtil.GetInt(3, 6);
}
else
{
randomisedAmmoStackSize = _randomUtil.GetInt(
(int)inventoryMagGen.GetAmmoTemplate().Properties.StackMinRandom,
(int)inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom
);
}
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
inventoryMagGen.GetAmmoTemplate().Id,
@@ -19,7 +19,6 @@ public class ExternalInventoryMagGen(
RandomUtil _randomUtil
) : InventoryMagGen, IInventoryMagGen
{
public int GetPriority()
{
return 99;
@@ -61,10 +60,8 @@ public class ExternalInventoryMagGen(
);
if (fitsIntoInventory == ItemAddedResult.NO_CONTAINERS)
{
// No containers to fit magazines, stop trying
break;
}
// No space for magazine and we haven't reached desired magazine count
if (fitsIntoInventory == ItemAddedResult.NO_SPACE && i < randomizedMagazineCount)
@@ -73,9 +70,7 @@ public class ExternalInventoryMagGen(
if (fitAttempts > 5)
{
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Failed {fitAttempts} times to add magazine {magazineTpl} to bot inventory, stopping");
}
break;
}
@@ -85,25 +80,19 @@ public class ExternalInventoryMagGen(
* Temporary workaround to Killa spawning with no extra mags if he spawns with a drum mag */
if (magazineTpl == defaultMagazineTpl)
{
// We were already on default - stop here to prevent infinite loop
break;
}
// Add failed magazine tpl to blacklist
attemptedMagBlacklist.Add(magazineTpl);
if (defaultMagazineTpl is null)
{
// No default to fall back to, stop trying to add mags
break;
}
if (defaultMagazineTpl == BaseClasses.MAGAZINE)
{
// Magazine base type, do not use
break;
}
// Set chosen magazine tpl to the weapons default magazine tpl and try to fit into inventory next loop
magazineTpl = defaultMagazineTpl;
@@ -128,15 +117,12 @@ public class ExternalInventoryMagGen(
if (result?.Id is null)
{
// Highly likely shotgun has no external mags
if (isShotgun)
{
break;
}
if (isShotgun) break;
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Unable to add additional magazine into bot inventory: vest/pockets for weapon: {weapon.Name}, attempted: {fitAttempts} times. Reason: {fitsIntoInventory}");
}
_logger.Debug(
$"Unable to add additional magazine into bot inventory: vest/pockets for weapon: {weapon.Name}, attempted: {fitAttempts} times. Reason: {fitsIntoInventory}"
);
break;
}
@@ -151,10 +137,8 @@ public class ExternalInventoryMagGen(
}
if (fitsIntoInventory == ItemAddedResult.SUCCESS)
{
// Reset fit counter now it succeeded
fitAttempts = 0;
}
}
}
@@ -162,10 +146,7 @@ public class ExternalInventoryMagGen(
{
// The mag Slot data for the weapon
var magSlot = _itemHelper.GetItem(weaponTpl).Value.Properties.Slots.FirstOrDefault((x) => x.Name == "mod_magazine");
if (magSlot is null)
{
return null;
}
if (magSlot is null) return null;
// All possible mags that fit into the weapon excluding blacklisted
var magazinePool = magSlot.Props.Filters[0]
@@ -173,17 +154,11 @@ public class ExternalInventoryMagGen(
.Select(
(x) => _itemHelper.GetItem(x).Value
);
if (magazinePool is null)
{
return null;
}
if (magazinePool is null) return null;
// Non-internal magazines that fit into the weapon
var externalMagazineOnlyPool = magazinePool.Where((x) => x.Properties.ReloadMagType != ReloadMode.InternalMagazine);
if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0)
{
return null;
}
if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0) return null;
// Randomly chosen external magazine
return _randomUtil.GetArrayValue(externalMagazineOnlyPool);
@@ -7,7 +7,6 @@ namespace Core.Generators.WeaponGen.Implementations;
[Injectable]
public class UbglExternalMagGen(
BotWeaponGeneratorHelper _botWeaponGeneratorHelper
) : InventoryMagGen, IInventoryMagGen
{
public int GetPriority()
@@ -104,10 +104,7 @@ public class WeatherGenerator(
protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason)
{
var result = _weatherConfig.Weather.SeasonValues.TryGetValue(currentSeason.ToString(), out var value);
if (!result)
{
return _weatherConfig.Weather.SeasonValues["default"];
}
if (!result) return _weatherConfig.Weather.SeasonValues["default"];
return value!;
}
+7 -33
View File
@@ -47,17 +47,11 @@ public class AssortHelper(
{
// Get quest id that unlocks assort + statuses quest can be in to show assort
var unlockValues = GetQuestIdAndStatusThatShowAssort(mergedQuestAssorts, assortId.Key);
if (unlockValues is null)
{
continue;
}
if (unlockValues is null) continue;
// Remove assort if quest in profile does not have status that unlocks assort
var questStatusInProfile = _questHelper.GetQuestStatus(pmcProfile, unlockValues.Value.Key);
if (!unlockValues.Value.Value.Contains(questStatusInProfile))
{
strippedTraderAssorts = RemoveItemFromAssort(traderAssorts, assortId.Key, flea);
}
if (!unlockValues.Value.Value.Contains(questStatusInProfile)) strippedTraderAssorts = RemoveItemFromAssort(traderAssorts, assortId.Key, flea);
}
return strippedTraderAssorts;
@@ -74,29 +68,23 @@ public class AssortHelper(
string assortId)
{
if (mergedQuestAssorts.TryGetValue("started", out var dict1) && dict1.ContainsKey(assortId))
{
// Assort unlocked by starting quest, assort is visible to player when : started or ready to hand in + handed in
return new KeyValuePair<string, List<QuestStatusEnum>>(
mergedQuestAssorts["started"][assortId],
[QuestStatusEnum.Started, QuestStatusEnum.AvailableForFinish, QuestStatusEnum.Success]
);
}
if (mergedQuestAssorts.TryGetValue("success", out var dict2) && dict2.ContainsKey(assortId))
{
return new KeyValuePair<string, List<QuestStatusEnum>>(
mergedQuestAssorts["success"][assortId],
[QuestStatusEnum.Success]
);
}
if (mergedQuestAssorts.TryGetValue("fail", out var dict3) && dict3.ContainsKey(assortId))
{
return new KeyValuePair<string, List<QuestStatusEnum>>(
mergedQuestAssorts["fail"][assortId],
[QuestStatusEnum.Fail]
);
}
return null;
}
@@ -122,12 +110,8 @@ public class AssortHelper(
// Remove items restricted by loyalty levels above those reached by the player
foreach (var item in assort.LoyalLevelItems)
{
if (pmcProfile.TradersInfo.TryGetValue(traderId, out var info) && assort.LoyalLevelItems[item.Key] > info.LoyaltyLevel)
{
strippedAssort = RemoveItemFromAssort(assort, item.Key);
}
}
return strippedAssort;
}
@@ -145,12 +129,8 @@ public class AssortHelper(
if (assort.BarterScheme.TryGetValue(itemID, out var lisToUse) && lisToUse is not null && flea)
{
foreach (var barterSchemes in lisToUse)
{
foreach (var barterScheme in barterSchemes)
{
barterScheme.SptQuestLocked = true;
}
}
foreach (var barterScheme in barterSchemes)
barterScheme.SptQuestLocked = true;
return assort;
}
@@ -159,15 +139,9 @@ public class AssortHelper(
assort.LoyalLevelItems.Remove(itemID);
foreach (var i in idsToRemove)
{
foreach (var a in assort.Items.ToList())
{
if (a.Id == i)
{
assort.Items.Remove(a);
}
}
}
foreach (var a in assort.Items.ToList())
if (a.Id == i)
assort.Items.Remove(a);
return assort;
}
+16 -8
View File
@@ -22,7 +22,7 @@ public class BotDifficultyHelper(
)
{
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
/// <summary>
/// Get difficulty settings for desired bot type, if not found use assault bot types
/// </summary>
@@ -33,7 +33,8 @@ public class BotDifficultyHelper(
public DifficultyCategories GetBotDifficultySettings(string type, string desiredDifficulty, Bots botDb)
{
var desiredType = type.ToLower();
if (!botDb.Types.ContainsKey(desiredType)) {
if (!botDb.Types.ContainsKey(desiredType))
{
// No bot found, get fallback difficulty values
_logger.Warning(_localisationService.GetText("bot-unable_to_get_bot_fallback_to_assault", type));
botDb.Types[desiredType] = _cloner.Clone(botDb.Types["assault"]);
@@ -42,13 +43,19 @@ public class BotDifficultyHelper(
// Get settings from raw bot json template file
var botTemplate = _botHelper.GetBotTemplate(desiredType);
botTemplate.BotDifficulty.TryGetValue(desiredDifficulty, out var difficultySettings);
if (difficultySettings is null) {
if (difficultySettings is null)
{
// No bot settings found, use 'assault' bot difficulty instead
_logger.Warning(
_localisationService.GetText("bot-unable_to_get_bot_difficulty_fallback_to_assault", new {
botType = desiredType,
difficulty = desiredDifficulty,
}));
_localisationService.GetText(
"bot-unable_to_get_bot_difficulty_fallback_to_assault",
new
{
botType = desiredType,
difficulty = desiredDifficulty
}
)
);
botDb.Types[desiredType].BotDifficulty[desiredDifficulty] = _cloner.Clone(
botDb.Types["assault"].BotDifficulty[desiredDifficulty]
);
@@ -82,7 +89,8 @@ public class BotDifficultyHelper(
/// <returns>bot difficulty</returns>
public string ConvertBotDifficultyDropdownToBotDifficulty(string dropDownDifficulty)
{
switch (dropDownDifficulty.ToLower()) {
switch (dropDownDifficulty.ToLower())
{
case "medium":
return "normal";
case "random":
+23 -81
View File
@@ -45,10 +45,7 @@ public class BotGeneratorHelper(
var raidIsNight = raidSettings?.TimeVariant == DateTimeEnum.PAST;
RandomisedResourceDetails randomisationSettings = null;
if (botRole is not null)
{
_botConfig.LootItemResourceRandomization.TryGetValue(botRole, out randomisationSettings);
}
if (botRole is not null) _botConfig.LootItemResourceRandomization.TryGetValue(botRole, out randomisationSettings);
Upd itemProperties = new();
@@ -109,7 +106,7 @@ public class BotGeneratorHelper(
HpPercent = GetRandomizedResourceValue(
itemTemplate.Properties.MaxResource ?? 0,
randomisationSettings?.Food
),
)
};
hasProperties = true;
}
@@ -120,7 +117,7 @@ public class BotGeneratorHelper(
var lightLaserActiveChance = raidIsNight
? GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50)
: GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25);
itemProperties.Light = new UpdLight { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0, };
itemProperties.Light = new UpdLight { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 };
hasProperties = true;
}
else if (itemTemplate?.Parent == BaseClasses.TACTICAL_COMBO)
@@ -134,7 +131,7 @@ public class BotGeneratorHelper(
itemProperties.Light = new UpdLight
{
IsActive = _randomUtil.GetChance100(lightLaserActiveChance),
SelectedMode = 0,
SelectedMode = 0
};
hasProperties = true;
}
@@ -173,15 +170,9 @@ public class BotGeneratorHelper(
/// <returns>Randomized value from maxHpResource</returns>
private double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues? randomizationValues)
{
if (randomizationValues is null)
{
return maxResource;
}
if (randomizationValues is null) return maxResource;
if (_randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent))
{
return maxResource;
}
if (_randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent)) return maxResource;
return _randomUtil.GetInt(
(int)_randomUtil.GetPercentOfValue(randomizationValues.ResourcePercent, maxResource, 0),
@@ -198,10 +189,7 @@ public class BotGeneratorHelper(
/// <returns>Percent chance to be active</returns>
private double? GetBotEquipmentSettingFromConfig(string? botRole, string setting, double defaultValue)
{
if (botRole is null)
{
return defaultValue;
}
if (botRole is null) return defaultValue;
var botEquipmentSettings = _botConfig.Equipment[GetBotEquipmentRole(botRole)];
if (botEquipmentSettings is null)
@@ -299,10 +287,7 @@ public class BotGeneratorHelper(
{
// Skip slots that have no incompatibilities
List<string> slotsToCheck = ["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"];
if (slotsToCheck.Contains(equipmentSlot))
{
return new ChooseRandomCompatibleModResult { Incompatible = false, Found = false, Reason = "" };
}
if (slotsToCheck.Contains(equipmentSlot)) return new ChooseRandomCompatibleModResult { Incompatible = false, Found = false, Reason = "" };
// TODO: Can probably be optimized to cache itemTemplates as items are added to inventory
var equippedItemsDb = itemsEquipped.Select(equippedItem => _itemHelper.GetItem(equippedItem.Template).Value).ToList();
@@ -316,7 +301,7 @@ public class BotGeneratorHelper(
new
{
itemTpl = tplToCheck,
slot = equipmentSlot,
slot = equipmentSlot
}
)
);
@@ -333,7 +318,7 @@ public class BotGeneratorHelper(
{
id = itemToEquip?.Id,
name = itemToEquip?.Name,
slot = equipmentSlot,
slot = equipmentSlot
}
)
);
@@ -347,20 +332,16 @@ public class BotGeneratorHelper(
item => item?.Properties?.GetType().GetProperties().FirstOrDefault(x => x.Name.ToLower() == $"blocks{equipmentSlot}")?.GetValue(item) is not null
);
if (blockingItem is not null)
{
// this.logger.warning(`1 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._name} - {equipmentSlot}`);
return new()
return new ChooseRandomCompatibleModResult
{
Incompatible = true, Found = false,
Reason = $"{tplToCheck} {itemToEquip.Name} in slot: {equipmentSlot} blocked by: {blockingItem.Id} {blockingItem.Name}", SlotBlocked = true
};
}
// Check if any of the current inventory templates have the incoming item defined as incompatible
blockingItem = templateItems.FirstOrDefault(x => x?.Properties?.ConflictingItems?.Contains(tplToCheck) ?? false);
if (blockingItem is not null)
{
// this.logger.warning(`2 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._props.Name} - {equipmentSlot}`);
return new ChooseRandomCompatibleModResult
{
@@ -369,14 +350,12 @@ public class BotGeneratorHelper(
Reason = $"{tplToCheck} {itemToEquip.Name} in slot: {equipmentSlot} blocked by: {blockingItem.Id} {blockingItem.Name}",
SlotBlocked = true
};
}
// Does item being checked get blocked/block existing item
if (itemToEquip.Properties.BlocksHeadwear ?? false)
{
var existingHeadwear = itemsEquipped.FirstOrDefault((x) => x.SlotId == "Headwear");
if (existingHeadwear is not null)
{
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
@@ -384,7 +363,6 @@ public class BotGeneratorHelper(
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingHeadwear.Template} in slot: {existingHeadwear.SlotId}",
SlotBlocked = true
};
}
}
// Does item being checked get blocked/block existing item
@@ -392,15 +370,13 @@ public class BotGeneratorHelper(
{
var existingFaceCover = itemsEquipped.FirstOrDefault((item) => item.SlotId == "FaceCover");
if (existingFaceCover is not null)
{
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
Found = false,
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingFaceCover.Template} in slot: {existingFaceCover.SlotId}",
SlotBlocked = true,
SlotBlocked = true
};
}
}
// Does item being checked get blocked/block existing item
@@ -408,15 +384,13 @@ public class BotGeneratorHelper(
{
var existingEarpiece = itemsEquipped.FirstOrDefault((item) => item.SlotId == "Earpiece");
if (existingEarpiece is not null)
{
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
Found = false,
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingEarpiece.Template} in slot: {existingEarpiece.SlotId}",
SlotBlocked = true,
SlotBlocked = true
};
}
}
// Does item being checked get blocked/block existing item
@@ -424,29 +398,25 @@ public class BotGeneratorHelper(
{
var existingArmorVest = itemsEquipped.FirstOrDefault((item) => item.SlotId == "ArmorVest");
if (existingArmorVest is not null)
{
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
Found = false,
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingArmorVest.Template} in slot: {existingArmorVest.SlotId}",
SlotBlocked = true,
SlotBlocked = true
};
}
}
// Check if the incoming item has any inventory items defined as incompatible
var blockingInventoryItem = itemsEquipped.FirstOrDefault((x) => itemToEquip.Properties.ConflictingItems?.Contains(x.Template) ?? false);
if (blockingInventoryItem is not null)
{
// this.logger.warning(`3 incompatibility found between - {itemToEquip[1]._name} and {blockingInventoryItem._tpl} - {equipmentSlot}`)
return new ChooseRandomCompatibleModResult
{
Incompatible = true,
Found = false,
Reason = $"{tplToCheck} blocks existing item {blockingInventoryItem.Template} in slot {blockingInventoryItem.SlotId}",
Reason = $"{tplToCheck} blocks existing item {blockingInventoryItem.Template} in slot {blockingInventoryItem.SlotId}"
};
}
return new ChooseRandomCompatibleModResult { Incompatible = false, Reason = "" };
}
@@ -488,13 +458,10 @@ public class BotGeneratorHelper(
var missingContainerCount = 0;
foreach (var equipmentSlotId in equipmentSlots)
{
if (containersIdFull?.Contains(equipmentSlotId.ToString()) ?? false)
{
continue;
}
if (containersIdFull?.Contains(equipmentSlotId.ToString()) ?? false) continue;
// Get container to put item into
var container = (inventory.Items).FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
var container = inventory.Items.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
if (container is null)
{
missingContainerCount++;
@@ -502,11 +469,9 @@ public class BotGeneratorHelper(
{
// Bot doesn't have any containers we want to add item to
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug(
$"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
);
}
return ItemAddedResult.NO_CONTAINERS;
}
@@ -526,10 +491,8 @@ public class BotGeneratorHelper(
}
if (value?.Properties?.Grids?.Count == 0)
{
// Container has no slots to hold items
continue;
}
// Get x/y grid size of item
var itemSize = _inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildren);
@@ -543,16 +506,12 @@ public class BotGeneratorHelper(
if (slotGrid.Props?.CellsH == 0 ||
slotGrid.Props?.CellsV == 0 ||
itemSize[0] * itemSize[1] > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH)
{
continue;
}
// Can't put item type in grid, skip all grids as we're assuming they have the same rules
if (!ItemAllowedInContainer(slotGrid, rootItemTplId))
{
// Multiple containers, maybe next one allows item, only break out of loop for the containers grids
break;
}
// Get all root items in found container
var existingContainerItems = (inventory.Items ?? []).Where(
@@ -591,7 +550,7 @@ public class BotGeneratorHelper(
{
X = findSlotResult.X,
Y = findSlotResult.Y,
R = (findSlotResult.Rotation ?? false) ? 1 : 0,
R = findSlotResult.Rotation ?? false ? 1 : 0
}
;
}
@@ -603,10 +562,7 @@ public class BotGeneratorHelper(
}
// If we've checked all grids in container and reached this point, there's no space for item
if (currentGridCount >= totalSlotGridCount)
{
break;
}
if (currentGridCount >= totalSlotGridCount) break;
currentGridCount++;
// No space in this grid, move to next container grid and try again
@@ -616,10 +572,7 @@ public class BotGeneratorHelper(
if (containersIdFull is null) continue;
// 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)
{
containersIdFull.Add(equipmentSlotId.ToString());
}
if (itemSize[0] == 1 && itemSize[1] == 1) containersIdFull.Add(equipmentSlotId.ToString());
}
return ItemAddedResult.NO_SPACE;
@@ -660,31 +613,20 @@ public class BotGeneratorHelper(
var filter = propFilters?.FirstOrDefault()?.Filter ?? [];
if (propFilters?.Count == 0)
{
// no filters, item is fine to add
return true;
}
// Check if item base type is excluded
var itemDetails = _itemHelper.GetItem(itemTpl).Value;
// if item to add is found in exclude filter, not allowed
if (excludedFilter.Contains(itemDetails?.Parent ?? string.Empty))
{
return false;
}
if (excludedFilter.Contains(itemDetails?.Parent ?? string.Empty)) return false;
// If Filter array only contains 1 filter and its for basetype 'item', allow it
if (filter.Count == 1 && filter.Contains(BaseClasses.ITEM))
{
return true;
}
if (filter.Count == 1 && filter.Contains(BaseClasses.ITEM)) return true;
// If allowed filter has something in it + filter doesnt have basetype 'item', not allowed
if (filter.Count > 0 && !filter.Contains(itemDetails?.Parent ?? string.Empty))
{
return false;
}
if (filter.Count > 0 && !filter.Contains(itemDetails?.Parent ?? string.Empty)) return false;
return true;
}
+6 -25
View File
@@ -74,10 +74,7 @@ public class BotHelper(
var friendlyBotTypesKey = "FRIENDLY_BOT_TYPES";
// Null guard
if (difficultySettings.Mind[friendlyBotTypesKey] is null)
{
difficultySettings.Mind[friendlyBotTypesKey] = new List<string>();
}
if (difficultySettings.Mind[friendlyBotTypesKey] is null) difficultySettings.Mind[friendlyBotTypesKey] = new List<string>();
((List<string>)difficultySettings.Mind[friendlyBotTypesKey]).Add(typeToAdd);
}
@@ -92,25 +89,15 @@ public class BotHelper(
var revengePropKey = "REVENGE_BOT_TYPES";
// Nothing to add
if (typesToAdd is null)
{
return;
}
if (typesToAdd is null) return;
// Null guard
if (difficultySettings.Mind[revengePropKey] is null)
{
difficultySettings.Mind[revengePropKey] = new List<string>();
}
if (difficultySettings.Mind[revengePropKey] is null) difficultySettings.Mind[revengePropKey] = new List<string>();
var revengeArray = (List<string>)difficultySettings.Mind[revengePropKey];
foreach (var botTypeToAdd in typesToAdd)
{
if (!revengeArray.Contains(botTypeToAdd))
{
revengeArray.Add(botTypeToAdd);
}
}
}
public bool RollChanceToBePmc(MinMax botConvertMinMax)
@@ -121,10 +108,7 @@ public class BotHelper(
protected Dictionary<string, MinMax> GetPmcConversionValuesForLocation(string location)
{
var result = _pmcConfig.ConvertIntoPmcChance[location.ToLower()];
if (result is null)
{
_pmcConfig.ConvertIntoPmcChance = new();
}
if (result is null) _pmcConfig.ConvertIntoPmcChance = new Dictionary<string, Dictionary<string, MinMax>>();
return result;
}
@@ -151,10 +135,7 @@ public class BotHelper(
public RandomisationDetails GetBotRandomizationDetails(int botLevel, EquipmentFilters botEquipConfig)
{
// No randomisation details found, skip
if (botEquipConfig is null || botEquipConfig.Randomisation is null)
{
return null;
}
if (botEquipConfig is null || botEquipConfig.Randomisation is null) return null;
return botEquipConfig.Randomisation.FirstOrDefault(
(randDetails) => botLevel >= randDetails.LevelRange.Min && botLevel <= randDetails.LevelRange.Max
@@ -203,7 +184,7 @@ public class BotHelper(
/// <returns>name of PMC</returns>
public string GetPmcNicknameOfMaxLength(int maxLength, string side = null)
{
var randomType = (side is not null) ? side : (_randomUtil.GetInt(0, 1) == 0) ? "usec" : "bear";
var randomType = side is not null ? side : _randomUtil.GetInt(0, 1) == 0 ? "usec" : "bear";
var allNames = _databaseService.GetBots().Types[randomType.ToLower()].FirstNames;
var filteredNames = allNames.Where((name) => name.Length <= maxLength);
if (filteredNames.Count() == 0)
@@ -113,11 +113,11 @@ public class BotWeaponGeneratorHelper(
equipmentSlotsToAddTo = [EquipmentSlots.TacticalVest, EquipmentSlots.Pockets];
var ammoItems = _itemHelper.SplitStack(
new()
new Item
{
Id = _hashUtil.Generate(),
Template = ammoTpl,
Upd = new() { StackObjectsCount = cartridgeCount },
Upd = new Upd { StackObjectsCount = cartridgeCount }
}
);
@@ -136,10 +136,8 @@ public class BotWeaponGeneratorHelper(
_logger.Debug($"Unable to add ammo: {ammoItem.Template} to bot inventory, {result.ToString()}");
if (result == ItemAddedResult.NO_SPACE || result == ItemAddedResult.NO_CONTAINERS)
{
// If there's no space for 1 stack or no containers to hold item, there's no space for the others
break;
}
}
}
}
+12 -32
View File
@@ -23,29 +23,20 @@ public class ContainerHelper
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);
}
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;
}
// Try each slot on the row (across = x)
for (var x = 0; x < limitX; x++)
{
var foundSlot = LocateSlot(container2D, containerX, containerY, x, y, itemWidth, itemHeight);
if (foundSlot)
{
return new FindSlotResult(true, x, y, rotation);
}
if (foundSlot) return new FindSlotResult(true, x, y, rotation);
// Failed to find slot, rotate item and try again
if (!foundSlot && ItemBiggerThan1X1(itemWidth, itemHeight))
@@ -118,10 +109,7 @@ public class ContainerHelper
}
}
if (!foundSlot)
{
break;
}
if (!foundSlot) break;
}
return foundSlot;
@@ -149,20 +137,12 @@ public class ContainerHelper
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");
}
}
}
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");
}
}
@@ -187,13 +167,13 @@ public class 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; }
}
@@ -30,14 +30,9 @@ public abstract class AbstractDialogChatBot(
var commandos = _chatCommands.Where(c => c.GetCommandPrefix() == splitCommand.FirstOrDefault());
if (commandos.FirstOrDefault()?.GetCommands().Contains(splitCommand[1]) ?? false)
{
return commandos.FirstOrDefault().Handle(splitCommand[1], GetChatBot(), sessionId, request);
}
if (splitCommand.FirstOrDefault()?.ToLower() == "help")
{
return SendPlayerHelpMessage(sessionId, request);
}
if (splitCommand.FirstOrDefault()?.ToLower() == "help") return SendPlayerHelpMessage(sessionId, request);
_mailSendService.SendUserMessageToPlayer(
sessionId,
@@ -77,7 +72,6 @@ public abstract class AbstractDialogChatBot(
() =>
{
foreach (var subCommand in chatCommand.GetCommands())
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
@@ -85,7 +79,6 @@ public abstract class AbstractDialogChatBot(
[],
null
);
}
},
TimeSpan.FromSeconds(1)
);
@@ -100,11 +93,9 @@ public abstract class AbstractDialogChatBot(
public void RegisterChatCommand(IChatCommand chatCommand)
{
if (_chatCommands.Any(cc => cc.GetCommandPrefix() == chatCommand.GetCommandPrefix()))
{
throw new Exception(
$"The command \"{chatCommand.GetCommandPrefix()}\" attempting to be registered already exists."
);
}
_chatCommands.Add(chatCommand);
}
@@ -13,6 +13,7 @@ public class SptCommandoCommands : IChatCommand
{
protected List<ISptCommand> _sptCommands;
protected LocalisationService _localisationService;
public SptCommandoCommands(
ConfigServer configServer,
LocalisationService localisationService,
@@ -33,14 +34,13 @@ public class SptCommandoCommands : IChatCommand
public void RegisterSptCommandoCommand(ISptCommand command)
{
if (_sptCommands.Any((c) => c.GetCommand() == command.GetCommand())) {
if (_sptCommands.Any((c) => c.GetCommand() == command.GetCommand()))
throw new Exception(
_localisationService.GetText(
"chat-unable_to_register_command_already_registered",
command.GetCommand()
)
);
}
_sptCommands.Add(command);
}
@@ -104,10 +104,7 @@ public class GiveSptCommand(
else
{
// A new give request was entered, we need to ignore the old saved command
if (_savedCommand.ContainsKey(sessionId))
{
_savedCommand.Remove(sessionId);
}
if (_savedCommand.ContainsKey(sessionId)) _savedCommand.Remove(sessionId);
isItemName = result.Groups[5].Value != null;
item = result.Groups[5].Value is not null ? result.Groups[5].Value : result.Groups[2].Value;
@@ -7,7 +7,6 @@ public class SavedCommand
{
public SavedCommand()
{
}
public SavedCommand(int quantity, List<string> potentialItemNames, string locale)
@@ -9,22 +9,25 @@ public static class StringSimilarity
*/
public static double Match(string str1, string str2, int substringLength = 2, bool caseSensitive = false)
{
if (!caseSensitive) {
if (!caseSensitive)
{
str1 = str1.ToLower();
str2 = str2.ToLower();
}
if (str1.Length < substringLength || str2.Length < substringLength)
return 0;
var map = new Dictionary<string, int>();
for (var i = 0; i < str1.Length - (substringLength - 1); i++) {
for (var i = 0; i < str1.Length - (substringLength - 1); i++)
{
var substr1 = str1.Substring(i, substringLength);
map.Add(substr1, map.TryGetValue(substr1, out var value) ? value + 1 : 1);
}
var match = 0;
for (var j = 0; j < str2.Length - (substringLength - 1); j++) {
for (var j = 0; j < str2.Length - (substringLength - 1); j++)
{
var substr2 = str2.Substring(j, substringLength);
var count = map.GetValueOrDefault(substr2, 0);
if (count > 0)
@@ -34,7 +37,6 @@ public static class StringSimilarity
}
}
return match * 2d / (str1.Length + str2.Length - ((substringLength - 1d) * 2d));
return match * 2d / (str1.Length + str2.Length - (substringLength - 1d) * 2d);
}
}
@@ -5,31 +5,31 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SptMessageHandlers
namespace Core.Helpers.Dialogue.SptMessageHandlers;
[Injectable]
public class AreYouABotMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
{
[Injectable]
public class AreYouABotMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
public int GetPriority()
{
public int GetPriority()
{
return 100;
}
return 100;
}
public bool CanHandle(string message)
{
return message.ToLower() == "are you a bot";
}
public bool CanHandle(string message)
{
return message.ToLower() == "are you a bot";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(["beep boop", "**sad boop**", "probably", "sometimes", "yeah lol"]),
[], null
);
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(["beep boop", "**sad boop**", "probably", "sometimes", "yeah lol"]),
[],
null
);
}
}
@@ -5,38 +5,39 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class ForceChristmasMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
SeasonalEventService _seasonalEventService) : IChatMessageHandler
{
[Injectable]
public class ForceChristmasMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
SeasonalEventService _seasonalEventService) : IChatMessageHandler
public int GetPriority()
{
public int GetPriority()
{
return 99;
}
return 99;
}
public bool CanHandle(string message)
{
return message.ToLower() == "hohoho";
}
public bool CanHandle(string message)
{
return message.ToLower() == "hohoho";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
var enableEventResult = _seasonalEventService.ForceSeasonalEvent(SeasonalEventType.Christmas);
if (enableEventResult)
{
_mailSendService.SendUserMessageToPlayer(
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
var enableEventResult = _seasonalEventService.ForceSeasonalEvent(SeasonalEventType.Christmas);
if (enableEventResult)
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
sptFriendUser,
_randomUtil.GetArrayValue(
[
_localisationService.GetText("chatbot-forced_event_enabled", SeasonalEventType.Christmas)
]), [], null
);
}
}
]
),
[],
null
);
}
}
@@ -5,40 +5,39 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class ForceHalloweenMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
SeasonalEventService _seasonalEventService) : IChatMessageHandler
{
[Injectable]
public class ForceHalloweenMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
SeasonalEventService _seasonalEventService) : IChatMessageHandler
public int GetPriority()
{
public int GetPriority()
{
return 99;
}
return 99;
}
public bool CanHandle(string message)
{
return message.ToLower() == "veryspooky";
}
public bool CanHandle(string message)
{
return message.ToLower() == "veryspooky";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
var enableEventResult = _seasonalEventService.ForceSeasonalEvent(SeasonalEventType.Halloween);
if (enableEventResult)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
var enableEventResult = _seasonalEventService.ForceSeasonalEvent(SeasonalEventType.Halloween);
if (enableEventResult)
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(
[
_localisationService.GetText("chatbot-forced_event_enabled", SeasonalEventType.Halloween)
]),
[],
null
);
}
}
]
),
[],
null
);
}
}
@@ -7,38 +7,37 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class ForceSnowMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
ConfigServer _configServer) : IChatMessageHandler
{
[Injectable]
public class ForceSnowMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
ConfigServer _configServer) : IChatMessageHandler
private WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>();
public int GetPriority()
{
private WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>();
return 99;
}
public int GetPriority()
{
return 99;
}
public bool CanHandle(string message)
{
return message.ToLower() == "itsonlysnowalan";
}
public bool CanHandle(string message)
{
return message.ToLower() == "itsonlysnowalan";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_weatherConfig.OverrideSeason = Season.WINTER;
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_weatherConfig.OverrideSeason = Season.WINTER;
_mailSendService.SendUserMessageToPlayer(
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([_localisationService.GetText("chatbot-snow_enabled")]),
_randomUtil.GetArrayValue([_localisationService.GetText("chatbot-snow_enabled")]),
[],
null
);
}
);
}
}
@@ -7,38 +7,37 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class ForceSummerMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
ConfigServer _configServer) : IChatMessageHandler
{
[Injectable]
public class ForceSummerMessageHandler(
LocalisationService _localisationService,
MailSendService _mailSendService,
RandomUtil _randomUtil,
ConfigServer _configServer) : IChatMessageHandler
private WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>();
public int GetPriority()
{
private WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>();
return 99;
}
public int GetPriority()
{
return 99;
}
public bool CanHandle(string message)
{
return message.ToLower() == "givemesunshine";
}
public bool CanHandle(string message)
{
return message.ToLower() == "givemesunshine";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_weatherConfig.OverrideSeason = Season.SUMMER;
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_weatherConfig.OverrideSeason = Season.SUMMER;
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([_localisationService.GetText("chatbot-summer_enabled")]),
[],
null
);
}
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([_localisationService.GetText("chatbot-summer_enabled")]),
[],
null
);
}
}
@@ -37,7 +37,9 @@ public class GiveMeSpaceMessageHandler(
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_localisationService.GetText("chatbot-cannot_accept_any_more_of_gift"), [], null
_localisationService.GetText("chatbot-cannot_accept_any_more_of_gift"),
[],
null
);
}
else
@@ -47,9 +49,13 @@ public class GiveMeSpaceMessageHandler(
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
_localisationService.GetText("chatbot-added_stash_rows_please_restart"),
]), [], null
_randomUtil.GetArrayValue(
[
_localisationService.GetText("chatbot-added_stash_rows_please_restart")
]
),
[],
null
);
_profileHelper.FlagGiftReceivedInProfile(sessionId, stashRowGiftId, maxGiftsToSendCount);
@@ -6,37 +6,38 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class HelloMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
{
[Injectable]
public class HelloMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
protected List<string> _listOfMessages = ["hello", "hi", "sup", "yo", "hey", "bonjour"];
public string GetCommand()
{
protected List<string> _listOfMessages = ["hello", "hi", "sup", "yo", "hey", "bonjour"];
return "hello";
}
public string GetAssociatedBotId()
{
return "6723fd51c5924c57ce0ca01f";
}
public string GetCommand()
{
return "hello";
}
public string GetCommandHelp()
{
return "'hello' replies to the player with a random greeting";
}
public string GetAssociatedBotId()
{
return "6723fd51c5924c57ce0ca01f";
}
public string GetCommandHelp()
{
return "'hello' replies to the player with a random greeting";
}
public string PerformAction(UserDialogInfo commandHandler, string sessionId, SendMessageRequest request)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
commandHandler,
_randomUtil.GetArrayValue([
public string PerformAction(UserDialogInfo commandHandler, string sessionId, SendMessageRequest request)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
commandHandler,
_randomUtil.GetArrayValue(
[
"Howdy",
"Hi",
"Greetings",
@@ -47,33 +48,33 @@ namespace Core.Helpers.Dialogue.SPTFriend.Commands
"Heyyyyy",
"Hey there",
"OH its you"
]),
[], null
);
]
),
[],
null
);
return request.DialogId;
}
return request.DialogId;
}
public int GetPriority()
{
return 100;
}
public bool CanHandle(string message)
{
return _listOfMessages.Contains(message, StringComparer.OrdinalIgnoreCase);
}
public int GetPriority()
{
return 100;
}
public bool CanHandle(string message)
{
return _listOfMessages.Contains(message, StringComparer.OrdinalIgnoreCase);
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(
[
"Howdy",
"Hi",
"Greetings",
@@ -84,10 +85,11 @@ namespace Core.Helpers.Dialogue.SPTFriend.Commands
"Heyyyyy",
"Hey there",
"OH its you",
$"Hello {sender?.Info?.Nickname}",
]),
[], null
);
}
$"Hello {sender?.Info?.Nickname}"
]
),
[],
null
);
}
}
@@ -27,12 +27,14 @@ public class LoveYouChatMessageHandler(
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
"That's quite forward but i love you too in a purely chatbot-human way",
"I love you too buddy :3!",
"uwu",
$"love you too {sender?.Info?.Nickname}",
]),
_randomUtil.GetArrayValue(
[
"That's quite forward but i love you too in a purely chatbot-human way",
"I love you too buddy :3!",
"uwu",
$"love you too {sender?.Info?.Nickname}"
]
),
[],
null
);
@@ -4,36 +4,39 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class NikitaMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
{
[Injectable]
public class NikitaMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil) : IChatMessageHandler
public int GetPriority()
{
public int GetPriority()
{
return 100;
}
return 100;
}
public bool CanHandle(string message)
{
return message.ToLower() == "nikita";
}
public bool CanHandle(string message)
{
return message.ToLower() == "nikita";
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(
[
"I know that guy!",
"Cool guy, he made EFT!",
"Legend",
"The mastermind of my suffering",
"Remember when he said webel-webel-webel-webel, classic Nikita moment",
]), [], null
);
}
"Remember when he said webel-webel-webel-webel, classic Nikita moment"
]
),
[],
null
);
}
}
@@ -7,65 +7,64 @@ using Core.Services;
using Core.Utils;
using SptCommon.Annotations;
namespace Core.Helpers.Dialogue.SPTFriend.Commands
namespace Core.Helpers.Dialogue.SPTFriend.Commands;
[Injectable]
public class SendGiftMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil,
GiftService _giftService,
ConfigServer _configServer) : IChatMessageHandler
{
[Injectable]
public class SendGiftMessageHandler(
MailSendService _mailSendService,
RandomUtil _randomUtil,
GiftService _giftService,
ConfigServer _configServer) : IChatMessageHandler
private CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
private string commandSent = string.Empty;
public int GetPriority()
{
private CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
private string commandSent = string.Empty;
return 1;
}
public int GetPriority()
public bool CanHandle(string message)
{
return _giftService.GiftExists(message.ToLower());
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
// Gifts may be disabled via config
if (!_coreConfig.Features.ChatbotFeatures.SptFriendGiftsEnabled) return;
var giftSent = _giftService.SendGiftToPlayer(sessionId, commandSent);
switch (giftSent)
{
return 1;
}
public bool CanHandle(string message)
{
return _giftService.GiftExists(message.ToLower());
}
public void Process(string sessionId, UserDialogInfo sptFriendUser, PmcData sender)
{
// Gifts may be disabled via config
if (!_coreConfig.Features.ChatbotFeatures.SptFriendGiftsEnabled)
{
return;
}
var giftSent = _giftService.SendGiftToPlayer(sessionId, commandSent);
switch (giftSent)
{
case GiftSentResult.SUCCESS:
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue([
case GiftSentResult.SUCCESS:
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(
[
"Hey! you got the right code!",
"A secret code, how exciting!",
"You found a gift code!",
"A gift code! incredible",
"A gift! what could it be!"]),
[],
null
);
"A gift! what could it be!"
]
),
[],
null
);
return;
case GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED:
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(["Looks like you already used that code", "You already have that!!"]),
[],
null
);
return;
case GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED:
_mailSendService.SendUserMessageToPlayer(
sessionId,
sptFriendUser,
_randomUtil.GetArrayValue(["Looks like you already used that code", "You already have that!!"]),
[],
null
);
return;
}
return;
}
}
}
@@ -33,7 +33,7 @@ public class SptDialogueChatBot(
}
protected CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
public UserDialogInfo GetChatBot()
{
@@ -57,12 +57,9 @@ public class SptDialogueChatBot(
var sender = _profileHelper.GetPmcProfile(sessionId);
var sptFriendUser = GetChatBot();
if (request.Text?.ToLower() == "help")
{
return SendPlayerHelpMessage(sessionId, request);
}
if (request.Text?.ToLower() == "help") return SendPlayerHelpMessage(sessionId, request);
var handler = _chatMessageHandlers.FirstOrDefault((v) => v.CanHandle(request.Text));
if (handler is not null)
{
@@ -70,7 +67,7 @@ public class SptDialogueChatBot(
return request.DialogId;
}
_mailSendService.SendUserMessageToPlayer(
sessionId,
@@ -115,7 +112,6 @@ public class SptDialogueChatBot(
() =>
{
foreach (var subCommand in chatCommand.GetCommands())
{
_mailSendService.SendUserMessageToPlayer(
sessionId,
GetChatBot(),
@@ -123,7 +119,6 @@ public class SptDialogueChatBot(
[],
null
);
}
},
TimeSpan.FromSeconds(1)
);
+1 -1
View File
@@ -96,7 +96,7 @@ public class DialogueHelper(
public Dictionary<string, Models.Eft.Profile.Dialogue> GetDialogsForProfile(string sessionId)
{
var profile = _profileHelper.GetFullProfile(sessionId);
return profile.DialogueRecords ?? (profile.DialogueRecords = new());
return profile.DialogueRecords ?? (profile.DialogueRecords = new Dictionary<string, Models.Eft.Profile.Dialogue>());
}
public Models.Eft.Profile.Dialogue? GetDialogueFromProfile(string profileId, string dialogueId)
+29 -103
View File
@@ -14,7 +14,6 @@ public class DurabilityLimitsHelper(
BotHelper _botHelper,
ConfigServer _configServer)
{
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
/// <summary>
@@ -39,25 +38,13 @@ public class DurabilityLimitsHelper(
public double GetRandomizedMaxArmorDurability(TemplateItem? itemTemplate, string? botRole = null)
{
var itemMaxDurability = itemTemplate.Properties.MaxDurability.Value;
if (botRole is null)
{
return itemMaxDurability;
}
if (botRole is null) return itemMaxDurability;
if (_botHelper.IsBotPmc(botRole))
{
return GenerateMaxPmcArmorDurability(itemMaxDurability);
}
if (_botHelper.IsBotPmc(botRole)) return GenerateMaxPmcArmorDurability(itemMaxDurability);
if (_botHelper.IsBotBoss(botRole))
{
return itemMaxDurability;
}
if (_botHelper.IsBotBoss(botRole)) return itemMaxDurability;
if (_botHelper.IsBotFollower(botRole))
{
return itemMaxDurability;
}
if (_botHelper.IsBotFollower(botRole)) return itemMaxDurability;
return itemMaxDurability;
}
@@ -83,30 +70,15 @@ public class DurabilityLimitsHelper(
/// <returns></returns>
private string GetDurabilityRole(string? botRole)
{
if (botRole is null)
{
return "default";
}
if (botRole is null) return "default";
if (_botHelper.IsBotPmc(botRole))
{
return "pmc";
}
if (_botHelper.IsBotPmc(botRole)) return "pmc";
if (_botHelper.IsBotBoss(botRole))
{
return "boss";
}
if (_botHelper.IsBotBoss(botRole)) return "boss";
if (_botHelper.IsBotFollower(botRole))
{
return "follower";
}
if (_botHelper.IsBotFollower(botRole)) return "follower";
if (_botHelper.IsBotZombie(botRole))
{
return "zombie";
}
if (_botHelper.IsBotZombie(botRole)) return "zombie";
var roleExistsInConfig = _botConfig.Durability.BotDurabilities.ContainsKey(botRole);
if (!roleExistsInConfig)
@@ -152,15 +124,9 @@ public class DurabilityLimitsHelper(
protected int GetLowestMaxWeaponFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Weapon.LowestMax;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Weapon.LowestMax;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Weapon.LowestMax;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Weapon.LowestMax;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability);
return durability.Weapon.LowestMax;
@@ -168,15 +134,9 @@ public class DurabilityLimitsHelper(
protected int GetHighestMaxWeaponDurabilityFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Weapon.HighestMax;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Weapon.HighestMax;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Weapon.HighestMax;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Weapon.HighestMax;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability);
return durability.Weapon.HighestMax;
@@ -189,7 +149,8 @@ public class DurabilityLimitsHelper(
var delta = _randomUtil.GetInt(minDelta, maxDelta);
var result = maxDurability - delta;
var durabilityValueMinLimit = Math.Round(
(GetMinWeaponLimitPercentFromConfig(botRole) / 100) * maxDurability);
GetMinWeaponLimitPercentFromConfig(botRole) / 100 * maxDurability
);
// Don't let weapon dura go below the percent defined in config
return result >= durabilityValueMinLimit ? result : durabilityValueMinLimit;
@@ -202,7 +163,8 @@ public class DurabilityLimitsHelper(
var delta = _randomUtil.GetInt(minDelta, maxDelta);
var result = maxDurability - delta;
var durabilityValueMinLimit = Math.Round(
(GetMinArmorLimitPercentFromConfig(botRole) / 100) * maxDurability);
GetMinArmorLimitPercentFromConfig(botRole) / 100 * maxDurability
);
// Don't let armor dura go below the percent defined in config
return result >= durabilityValueMinLimit ? result : durabilityValueMinLimit;
@@ -210,15 +172,9 @@ public class DurabilityLimitsHelper(
protected int GetMinWeaponDeltaFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Weapon.MinDelta;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Weapon.MinDelta;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Weapon.MinDelta;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Weapon.MinDelta;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -227,15 +183,9 @@ public class DurabilityLimitsHelper(
protected int GetMaxWeaponDeltaFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Weapon.MaxDelta;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Weapon.MaxDelta;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Weapon.MaxDelta;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Weapon.MaxDelta;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -244,15 +194,9 @@ public class DurabilityLimitsHelper(
protected int GetMinArmorDeltaFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Armor.MinDelta;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Armor.MinDelta;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Armor.MinDelta;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Armor.MinDelta;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -261,15 +205,9 @@ public class DurabilityLimitsHelper(
protected int GetMaxArmorDeltaFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Armor.MaxDelta;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Armor.MaxDelta;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Armor.MaxDelta;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Armor.MaxDelta;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -278,15 +216,9 @@ public class DurabilityLimitsHelper(
protected double GetMinArmorLimitPercentFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Armor.MinLimitPercent;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Armor.MinLimitPercent;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Armor.MinLimitPercent;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Armor.MinLimitPercent;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -295,15 +227,9 @@ public class DurabilityLimitsHelper(
protected double GetMinWeaponLimitPercentFromConfig(string? botRole = null)
{
if (botRole is null or "default")
{
return _botConfig.Durability.Default.Weapon.MinLimitPercent;
}
if (botRole is null or "default") return _botConfig.Durability.Default.Weapon.MinLimitPercent;
if (botRole == "pmc")
{
return _botConfig.Durability.Pmc.Weapon.MinLimitPercent;
}
if (botRole == "pmc") return _botConfig.Durability.Pmc.Weapon.MinLimitPercent;
_botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value);
@@ -5,5 +5,4 @@ namespace Core.Helpers;
[Injectable]
public class GameEventHelper
{
}
+33 -40
View File
@@ -18,7 +18,7 @@ public class HandbookHelper(
protected ItemConfig _itemConfig = _configServer.GetConfig<ItemConfig>();
protected bool _lookupCacheGenerated = false;
protected LookupCollection _handbookPriceCache = new();
/// <summary>
/// Create an in-memory cache of all items with associated handbook price in handbookPriceCache class
/// </summary>
@@ -26,16 +26,21 @@ public class HandbookHelper(
{
var handbook = _databaseService.GetHandbook();
// Add handbook overrides found in items.json config into db
foreach (var itemTplKey in _itemConfig.HandbookPriceOverride) {
foreach (var itemTplKey in _itemConfig.HandbookPriceOverride)
{
var data = _itemConfig.HandbookPriceOverride[itemTplKey.Key];
var itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == itemTplKey.Key);
if (itemToUpdate is null) {
handbook.Items.Add( new HandbookItem {
Id = itemTplKey.Key,
ParentId = data.ParentId,
Price = data.Price,
});
if (itemToUpdate is null)
{
handbook.Items.Add(
new HandbookItem
{
Id = itemTplKey.Key,
ParentId = data.ParentId,
Price = data.Price
}
);
itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == itemTplKey.Key);
}
@@ -43,22 +48,23 @@ public class HandbookHelper(
}
var handbookDbClone = _cloner.Clone(handbook);
foreach (var handbookItem in handbookDbClone.Items) {
foreach (var handbookItem in handbookDbClone.Items)
{
_handbookPriceCache.Items.ById.TryAdd(handbookItem.Id, handbookItem.Price ?? 0);
if (!_handbookPriceCache.Items.ByParent.TryGetValue(handbookItem.ParentId, out var _)) {
if (!_handbookPriceCache.Items.ByParent.TryGetValue(handbookItem.ParentId, out _))
_handbookPriceCache.Items.ByParent.TryAdd(handbookItem.ParentId, []);
}
_handbookPriceCache.Items.ByParent.TryGetValue(handbookItem.ParentId, out var array);
array.Add(handbookItem.Id);
}
foreach (var handbookCategory in handbookDbClone.Categories) {
foreach (var handbookCategory in handbookDbClone.Categories)
{
_handbookPriceCache.Categories.ById.TryAdd(handbookCategory.Id, handbookCategory.ParentId);
if (handbookCategory.ParentId is not null) {
if (!_handbookPriceCache.Categories.ByParent.TryGetValue(handbookCategory.ParentId, out var _)) {
if (handbookCategory.ParentId is not null)
{
if (!_handbookPriceCache.Categories.ByParent.TryGetValue(handbookCategory.ParentId, out _))
_handbookPriceCache.Categories.ByParent.TryAdd(handbookCategory.ParentId, []);
}
_handbookPriceCache.Categories.ByParent.TryGetValue(handbookCategory.ParentId, out var array);
array.Add(handbookCategory.Id);
@@ -80,28 +86,19 @@ public class HandbookHelper(
_lookupCacheGenerated = true;
}
if (_handbookPriceCache.Items.ById.TryGetValue(tpl, out var item))
{
return item;
}
if (_handbookPriceCache.Items.ById.TryGetValue(tpl, out var item)) return item;
var handbookItem = _databaseService.GetHandbook().Items?.FirstOrDefault(item => item.Id == tpl);
var handbookItem = _databaseService.GetHandbook().Items?.FirstOrDefault(item => item.Id == tpl);
if (handbookItem is null)
{
var newValue = 0;
if (!_handbookPriceCache.Items.ById.TryAdd(tpl, newValue))
{
_handbookPriceCache.Items.ById[tpl] = newValue;
}
if (!_handbookPriceCache.Items.ById.TryAdd(tpl, newValue)) _handbookPriceCache.Items.ById[tpl] = newValue;
return newValue;
}
if (!_handbookPriceCache.Items.ById.TryAdd(tpl, handbookItem.Price ?? 0))
{
_handbookPriceCache.Items.ById[tpl] = handbookItem.Price ?? 0;
}
if (!_handbookPriceCache.Items.ById.TryAdd(tpl, handbookItem.Price ?? 0)) _handbookPriceCache.Items.ById[tpl] = handbookItem.Price ?? 0;
return handbookItem.Price.Value;
}
@@ -109,9 +106,7 @@ public class HandbookHelper(
public double GetTemplatePriceForItems(List<Item> items)
{
var total = 0D;
foreach (var item in items) {
total += GetTemplatePrice(item.Template);
}
foreach (var item in items) total += GetTemplatePrice(item.Template);
return total;
}
@@ -124,7 +119,7 @@ public class HandbookHelper(
public List<string> TemplatesWithParent(string parentId)
{
_handbookPriceCache.Items.ByParent.TryGetValue(parentId, out var template);
return template ?? [];
}
@@ -135,7 +130,7 @@ public class HandbookHelper(
/// <returns>true if exists in cache</returns>
public bool IsCategory(string category)
{
return _handbookPriceCache.Categories.ById.TryGetValue(category, out var _);
return _handbookPriceCache.Categories.ById.TryGetValue(category, out _);
}
/// <summary>
@@ -157,9 +152,9 @@ public class HandbookHelper(
/// <returns>Count in roubles</returns>
public int InRUB(double nonRoubleCurrencyCount, string currencyTypeFrom)
{
return (int) (currencyTypeFrom == Money.ROUBLES
? nonRoubleCurrencyCount
: Math.Round(nonRoubleCurrencyCount * (GetTemplatePrice(currencyTypeFrom))));
return (int)(currencyTypeFrom == Money.ROUBLES
? nonRoubleCurrencyCount
: Math.Round(nonRoubleCurrencyCount * GetTemplatePrice(currencyTypeFrom)));
}
/// <summary>
@@ -170,13 +165,11 @@ public class HandbookHelper(
/// <returns>currency count in desired type</returns>
public int FromRUB(double roubleCurrencyCount, string currencyTypeTo)
{
if (currencyTypeTo == Money.ROUBLES) {
return (int) roubleCurrencyCount;
}
if (currencyTypeTo == Money.ROUBLES) return (int)roubleCurrencyCount;
// Get price of currency from handbook
var price = GetTemplatePrice(currencyTypeTo);
return (int) (price > 0 ? Math.Max(1, Math.Round(roubleCurrencyCount / price)) : 0);
return (int)(price > 0 ? Math.Max(1, Math.Round(roubleCurrencyCount / price)) : 0);
}
public HandbookCategory GetCategoryById(string handbookId)
+27 -63
View File
@@ -39,7 +39,7 @@ public class HealthHelper(
public void DefaultVitality(Vitality? vitality)
{
vitality ??= new Vitality { Health = null, Energy = 0, Temperature = 0, Hydration = 0 };
vitality.Health = new Dictionary<string, BodyPartHealth>
{
{
@@ -47,7 +47,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -57,7 +57,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -67,7 +67,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -77,7 +77,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -87,7 +87,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -97,7 +97,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -107,7 +107,7 @@ public class HealthHelper(
{
Health = new CurrentMinMax
{
Current = 0,
Current = 0
},
Effects = new Dictionary<string, BodyPartEffectProperties>()
}
@@ -153,20 +153,15 @@ public class HealthHelper(
{
// Effects
if (postRaidHealth.BodyParts[bodyPart.Key].Effects is not null)
{
fullProfile.VitalityData.Health[bodyPart.Key].Effects = postRaidHealth.BodyParts[bodyPart.Key].Effects;
}
// Limb hp
if (!isDead)
{
// Player alive, not is limb alive
fullProfile.VitalityData.Health[bodyPart.Key].Health.Current = postRaidHealth.BodyParts[bodyPart.Key].Health.Current ?? 0;
}
else
{
fullProfile.VitalityData.Health[bodyPart.Key].Health.Current = (pmcData.Health.BodyParts[bodyPart.Key].Health.Maximum * _healthConfig.HealthMultipliers.Death) ?? 0;
}
fullProfile.VitalityData.Health[bodyPart.Key].Health.Current =
pmcData.Health.BodyParts[bodyPart.Key].Health.Maximum * _healthConfig.HealthMultipliers.Death ?? 0;
}
TransferPostRaidLimbEffectsToProfile(postRaidHealth.BodyParts, pmcData);
@@ -217,26 +212,19 @@ public class HealthHelper(
if (profileBodyPartEffects.TryGetValue(effect.Key, out var dictEffect))
{
if (effectsToIgnore.Contains(effect.Key))
{
// Get rid of certain effects we dont want to persist out of raid
dictEffect = null;
}
continue;
}
if (effectsToIgnore.Contains(effect.Key))
{
// Do not pass some effects to out of raid profile
continue;
}
var effectToAdd = new BodyPartEffectProperties { Time = effectDetails.Time ?? -1 };
// Add effect to server profile
if (profileBodyPartEffects.TryAdd(effect.Key, effectToAdd))
{
profileBodyPartEffects[effect.Key] = effectToAdd;
}
if (profileBodyPartEffects.TryAdd(effect.Key, effectToAdd)) profileBodyPartEffects[effect.Key] = effectToAdd;
}
}
}
@@ -248,27 +236,16 @@ public class HealthHelper(
/// <param name="sessionID">Session id</param>
protected void SaveHealth(PmcData pmcData, string sessionID)
{
if (!_healthConfig.Save.Health) {
return;
}
if (!_healthConfig.Save.Health) return;
var profileHealth = _saveServer.GetProfile(sessionID).VitalityData;
if (profileHealth.Hydration > pmcData.Health.Hydration.Maximum)
{
profileHealth.Hydration = pmcData.Health.Hydration.Maximum;
}
if (profileHealth.Energy > pmcData.Health.Energy.Maximum)
{
profileHealth.Energy = pmcData.Health.Energy.Maximum;
}
if (profileHealth.Temperature > pmcData.Health.Temperature.Maximum)
{
profileHealth.Temperature = pmcData.Health.Temperature.Maximum;
}
if (profileHealth.Hydration > pmcData.Health.Hydration.Maximum) profileHealth.Hydration = pmcData.Health.Hydration.Maximum;
if (profileHealth.Energy > pmcData.Health.Energy.Maximum) profileHealth.Energy = pmcData.Health.Energy.Maximum;
if (profileHealth.Temperature > pmcData.Health.Temperature.Maximum) profileHealth.Temperature = pmcData.Health.Temperature.Maximum;
pmcData.Health.Hydration.Current = Math.Round(profileHealth.Hydration ?? 0);
pmcData.Health.Energy.Current = Math.Round(profileHealth.Energy ?? 0);
pmcData.Health.Temperature.Current = Math.Round(profileHealth.Temperature ?? 0);
@@ -276,15 +253,11 @@ public class HealthHelper(
foreach (var bodyPart in pmcData.Health.BodyParts)
{
if (profileHealth.Health[bodyPart.Key].Health.Maximum > bodyPart.Value.Health.Maximum)
{
profileHealth.Health[bodyPart.Key].Health.Maximum = bodyPart.Value.Health.Maximum;
}
if (profileHealth.Health[bodyPart.Key].Health.Current == 0)
{
profileHealth.Health[bodyPart.Key].Health.Current = bodyPart.Value.Health.Maximum * _healthConfig.HealthMultipliers.Blacked;
}
bodyPart.Value.Health.Current = Math.Round(profileHealth.Health[bodyPart.Key].Health.Current ?? 0);
}
}
@@ -305,29 +278,20 @@ public class HealthHelper(
bool deleteExistingEffects = true)
{
// TODO: this will need to change, typing is all fucked up
if (!_healthConfig.Save.Effects)
if (!_healthConfig.Save.Effects) return;
foreach (var bodyPart in bodyPartsWithEffects)
{
return;
}
foreach ( var bodyPart in bodyPartsWithEffects) {
// clear effects from profile bodyPart
if (deleteExistingEffects)
{
pmcData.Health.BodyParts[bodyPart.Key].Effects = new Dictionary<string, BodyPartEffectProperties>();
}
if (deleteExistingEffects) pmcData.Health.BodyParts[bodyPart.Key].Effects = new Dictionary<string, BodyPartEffectProperties>();
foreach ( var effectType in bodyPartsWithEffects[bodyPart.Key].Effects) {
foreach (var effectType in bodyPartsWithEffects[bodyPart.Key].Effects)
{
var time = effectType.Value.Time;
if (time is not null && time > 0)
{
AddEffect(pmcData, effectType, time);
}
else
{
AddEffect(pmcData, effectType);
}
}
}
}
@@ -343,7 +307,7 @@ public class HealthHelper(
{
var profileBodyPart = pmcData.Health.BodyParts[effectType.Key];
profileBodyPart.Effects ??= new Dictionary<string, BodyPartEffectProperties>();
profileBodyPart.Effects[effectType.Key] = new BodyPartEffectProperties { Time = duration };
}
}
+107 -230
View File
@@ -40,7 +40,7 @@ public class HideoutHelper(
public const string BitcoinProductionId = "5d5c205bd582a50d042a3c0e";
public const string WaterCollector = "5d5589c1f934db045e6c5492";
public const int MaxSkillPoint = 5000;
protected List<string> _idCheck = [HideoutHelper.BitcoinFarm, HideoutHelper.CultistCircleCraftId];
protected List<string> _idCheck = [BitcoinFarm, CultistCircleCraftId];
/// <summary>
/// Add production to profiles' Hideout.Production array
@@ -67,10 +67,7 @@ public class HideoutHelper(
// @Important: Here we need to be very exact:
// - normal recipe: Production time value is stored in attribute "productionType" with small "p"
// - scav case recipe: Production time value is stored in attribute "ProductionType" with capital "P"
if (pmcData.Hideout?.Production is null)
{
pmcData.Hideout.Production = new Dictionary<string, Production?>();
}
if (pmcData.Hideout?.Production is null) pmcData.Hideout.Production = new Dictionary<string, Production?>();
var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, body.RecipeId);
@@ -99,7 +96,7 @@ public class HideoutHelper(
{
Id = _hashUtil.Generate(),
Template = toolItem.Template,
Upd = toolItem.Upd,
Upd = toolItem.Upd
}
);
}
@@ -133,10 +130,7 @@ public class HideoutHelper(
// @Important: Here we need to be very exact:
// - normal recipe: Production time value is stored in attribute "productionType" with small "p"
// - scav case recipe: Production time value is stored in attribute "ProductionType" with capital "P"
if (pmcData.Hideout?.Production is null)
{
pmcData.Hideout.Production = new Dictionary<string, Production?>();
}
if (pmcData.Hideout?.Production is null) pmcData.Hideout.Production = new Dictionary<string, Production?>();
var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, body.RecipeId);
@@ -170,7 +164,7 @@ public class HideoutHelper(
Interrupted = false,
NeedFuelForAllProductionTime = needFuelForAllProductionTime, // Used when sending to client
needFuelForAllProductionTime = needFuelForAllProductionTime, // used when stored in production.json
SkipTime = 0,
SkipTime = 0
};
}
@@ -189,9 +183,7 @@ public class HideoutHelper(
// Find stash item and adjust tpl to new tpl from bonus
var stashItem = profileData.Inventory.Items.FirstOrDefault((x) => x.Id == profileData.Inventory.Stash);
if (stashItem is null)
{
_logger.Warning(_localisationService.GetText("hideout-unable_to_apply_stashsize_bonus_no_stash_found", profileData.Inventory.Stash));
}
stashItem.Template = bonus.TemplateId;
@@ -211,10 +203,7 @@ public class HideoutHelper(
// Add bonus to player bonuses array in profile
// EnergyRegeneration, HealthRegeneration, RagfairCommission, ScavCooldownTimer, SkillGroupLevelingBoost, ExperienceRate, QuestMoneyReward etc
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Adding bonus: {bonus.Type} to profile, value: {bonus.Value}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Adding bonus: {bonus.Type} to profile, value: {bonus.Value}");
profileData.Bonuses.Add(bonus);
}
@@ -226,7 +215,7 @@ public class HideoutHelper(
{
var pmcData = _profileHelper.GetPmcProfile(sessionID);
var hideoutProperties = GetHideoutProperties(pmcData);
pmcData.Hideout.SptUpdateLastRunTimestamp ??= _timeUtil.GetTimeStamp();
UpdateAreasWithResources(sessionID, pmcData, hideoutProperties);
@@ -250,7 +239,7 @@ public class HideoutHelper(
IsGeneratorOn = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.GENERATOR)?.Active ?? false,
WaterCollectorHasFilter = DoesWaterCollectorHaveFilter(
pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.WATER_COLLECTOR)
),
)
};
return hideoutProperties;
@@ -260,10 +249,8 @@ public class HideoutHelper(
{
// Can put filters in from L3
if (waterCollector.Level == 3)
{
// Has filter in at least one slot
return waterCollector.Slots.Any(slot => slot.Items is not null);
}
// No Filter
return false;
@@ -301,10 +288,7 @@ public class HideoutHelper(
}
// Skip processing (Don't skip continious crafts like bitcoin farm or cultist circle)
if (IsCraftComplete(craft))
{
continue;
}
if (IsCraftComplete(craft)) continue;
// Special handling required
if (IsCraftOfType(craft, HideoutAreas.SCAV_CASE))
@@ -366,9 +350,9 @@ public class HideoutHelper(
switch (hideoutType)
{
case HideoutAreas.WATER_COLLECTOR:
return craft.RecipeId == HideoutHelper.WaterCollector;
return craft.RecipeId == WaterCollector;
case HideoutAreas.BITCOIN_FARM:
return craft.RecipeId == HideoutHelper.BitcoinFarm;
return craft.RecipeId == BitcoinFarm;
case HideoutAreas.SCAV_CASE:
return craft.SptIsScavCase ?? false;
case HideoutAreas.CIRCLE_OF_CULTISTS:
@@ -387,10 +371,8 @@ public class HideoutHelper(
/// <returns>True when craft is complete</returns>
protected bool IsCraftComplete(Production craft)
{
return (
craft.Progress >= craft.ProductionTime &&
!_idCheck.Contains(craft.RecipeId)
);
return craft.Progress >= craft.ProductionTime &&
!_idCheck.Contains(craft.RecipeId);
}
/// <summary>
@@ -405,10 +387,7 @@ public class HideoutHelper(
HideoutProperties hideoutProperties)
{
var timeElapsed = GetTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.IsGeneratorOn);
if (hideoutProperties.WaterCollectorHasFilter)
{
pmcData.Hideout.Production[productionId].Progress += timeElapsed;
}
if (hideoutProperties.WaterCollectorHasFilter) pmcData.Hideout.Production[productionId].Progress += timeElapsed;
}
/// <summary>
@@ -425,10 +404,7 @@ public class HideoutHelper(
HideoutProperties hideoutProperties)
{
// Production is complete, no need to do any calculations
if (DoesProgressMatchProductionTime(pmcData, prodId))
{
return;
}
if (DoesProgressMatchProductionTime(pmcData, prodId)) return;
// Get seconds since last hideout update + now
var timeElapsed = GetTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.IsGeneratorOn, recipe);
@@ -440,10 +416,8 @@ public class HideoutHelper(
// Limit progress to total production time if progress is over (dont run for continious crafts))
if (!(recipe.Continuous ?? false))
{
// If progress is larger than prod time, return ProductionTime, hard cap the vaue
production.Progress = Math.Min(production.Progress ?? 0, production.ProductionTime ?? 0);
}
}
protected void UpdateCultistCircleCraftProgress(PmcData pmcData, string prodId)
@@ -451,10 +425,7 @@ public class HideoutHelper(
var production = pmcData.Hideout.Production[prodId];
// Check if we're already complete, skip
if (production.AvailableForFinish ?? false)
{
return;
}
if (production.AvailableForFinish ?? false) return;
// Get seconds since last hideout update
var timeElapsedSeconds = _timeUtil.GetTimeStamp() - pmcData.Hideout.SptUpdateLastRunTimestamp;
@@ -465,10 +436,7 @@ public class HideoutHelper(
production.Progress += timeElapsedSeconds;
// Check if craft is complete
if (production.Progress >= production.ProductionTime)
{
FlagCultistCircleCraftAsComplete(production);
}
if (production.Progress >= production.ProductionTime) FlagCultistCircleCraftAsComplete(production);
return;
}
@@ -525,14 +493,10 @@ public class HideoutHelper(
HideoutProperties hideoutProperties)
{
foreach (var area in pmcData.Hideout.Areas)
{
switch (area.Type)
{
case HideoutAreas.GENERATOR:
if (hideoutProperties.IsGeneratorOn)
{
UpdateFuel(area, pmcData, hideoutProperties.IsGeneratorOn);
}
if (hideoutProperties.IsGeneratorOn) UpdateFuel(area, pmcData, hideoutProperties.IsGeneratorOn);
break;
case HideoutAreas.WATER_COLLECTOR:
@@ -540,14 +504,10 @@ public class HideoutHelper(
break;
case HideoutAreas.AIR_FILTERING:
if (hideoutProperties.IsGeneratorOn)
{
UpdateAirFilters(area, pmcData, hideoutProperties.IsGeneratorOn);
}
if (hideoutProperties.IsGeneratorOn) UpdateAirFilters(area, pmcData, hideoutProperties.IsGeneratorOn);
break;
}
}
}
/// <summary>
@@ -579,10 +539,7 @@ public class HideoutHelper(
var combinedBonus = 1.0 - (fuelConsumptionBonusRate + hideoutManagementConsumptionBonusRate);
// Sanity check, never let fuel consumption go negative, otherwise it returns fuel to the player
if (combinedBonus < 0)
{
combinedBonus = 0;
}
if (combinedBonus < 0) combinedBonus = 0;
fuelUsedSinceLastTick *= combinedBonus;
@@ -592,24 +549,18 @@ public class HideoutHelper(
{
var generatorSlot = generatorArea.Slots[i];
if (generatorSlot?.Items is null)
{
// No item in slot, skip
continue;
}
var fuelItemInSlot = generatorSlot?.Items[0];
if (fuelItemInSlot is null)
{
// No item in slot, skip
continue;
}
var fuelRemaining = fuelItemInSlot.Upd?.Resource?.Value;
if (fuelRemaining == 0)
{
// No fuel left, skip
continue;
}
// Undefined fuel, fresh fuel item and needs its max fuel amount looked up
if (fuelRemaining is null)
@@ -626,7 +577,7 @@ public class HideoutHelper(
}
// Round values to keep accuracy
fuelRemaining = Math.Round((fuelRemaining * 10000) ?? 0) / 10000;
fuelRemaining = Math.Round(fuelRemaining * 10000 ?? 0) / 10000;
pointsConsumed = Math.Round(pointsConsumed * 10000) / 10000;
// Fuel consumed / 10 is over 1, add hideout management skill point
@@ -642,10 +593,7 @@ public class HideoutHelper(
// Deducted all used fuel from this container, clean up and exit loop
fuelItemInSlot.Upd = GetAreaUpdObject(1, fuelRemaining, pointsConsumed, isFuelItemFoundInRaid);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Profile: {pmcData.Id} Generator has: {fuelRemaining} fuel left in slot {i + 1}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Profile: {pmcData.Id} Generator has: {fuelRemaining} fuel left in slot {i + 1}");
hasFuelRemaining = true;
break; // Break to avoid updating all the fuel tanks
@@ -655,17 +603,11 @@ public class HideoutHelper(
// Ran out of fuel items to deduct fuel from
fuelUsedSinceLastTick = Math.Abs(fuelRemaining ?? 0);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Profile: {pmcData.Id} Generator ran out of fuel");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Profile: {pmcData.Id} Generator ran out of fuel");
}
// Out of fuel, flag generator as offline
if (!hasFuelRemaining)
{
generatorArea.Active = false;
}
if (!hasFuelRemaining) generatorArea.Active = false;
}
protected void UpdateWaterCollector(
@@ -675,18 +617,12 @@ public class HideoutHelper(
HideoutProperties hideoutProperties)
{
// Skip water collector when not level 3 (cant collect until 3)
if (area.Level != 3)
{
return;
}
if (area.Level != 3) return;
if (!hideoutProperties.WaterCollectorHasFilter)
{
return;
}
if (!hideoutProperties.WaterCollectorHasFilter) return;
// Canister with purified water craft exists
var purifiedWaterCraft = pmcData.Hideout.Production[HideoutHelper.WaterCollector];
var purifiedWaterCraft = pmcData.Hideout.Production[WaterCollector];
if (purifiedWaterCraft is not null && purifiedWaterCraft.GetType() == typeof(Production))
{
// Update craft time to account for increases in players craft time skill
@@ -702,13 +638,13 @@ public class HideoutHelper(
{
// continuousProductionStart()
// seem to not trigger consistently
HideoutSingleProductionStartRequestData recipe = new HideoutSingleProductionStartRequestData
var recipe = new HideoutSingleProductionStartRequestData
{
RecipeId = HideoutHelper.WaterCollector,
RecipeId = WaterCollector,
Action = "HideoutSingleProductionStart",
Items = [],
Tools = [],
Timestamp = _timeUtil.GetTimeStamp(),
Timestamp = _timeUtil.GetTimeStamp()
};
RegisterProduction(pmcData, recipe, sessionId);
@@ -742,8 +678,7 @@ public class HideoutHelper(
var timeReductionSeconds = 0D;
// Bitcoin farm is excluded from crafting skill cooldown reduction
if (recipeId != HideoutHelper.BitcoinFarm)
{
if (recipeId != BitcoinFarm)
// Seconds to deduct from crafts total time
timeReductionSeconds += GetSkillProductionTimeReduction(
pmcData,
@@ -751,30 +686,21 @@ public class HideoutHelper(
SkillTypes.Crafting,
globalSkillsDb.Crafting.ProductionTimeReductionPerLevel ?? 0
);
}
// Some crafts take into account hideout management, e.g. fuel, water/air filters
if (applyHideoutManagementBonus)
{
timeReductionSeconds += GetSkillProductionTimeReduction(
pmcData,
recipe.ProductionTime ?? 0,
SkillTypes.HideoutManagement,
globalSkillsDb.HideoutManagement.ConsumptionReductionPerLevel ?? 0
);
}
var modifiedProductionTime = recipe.ProductionTime - timeReductionSeconds;
if (modifiedProductionTime > 0 && _profileHelper.IsDeveloperAccount(pmcData.Id))
{
modifiedProductionTime = 40;
}
if (modifiedProductionTime > 0 && _profileHelper.IsDeveloperAccount(pmcData.Id)) modifiedProductionTime = 40;
// Sanity check, don't let anything craft in less than 5 seconds
if (modifiedProductionTime < 5)
{
modifiedProductionTime = 5;
}
if (modifiedProductionTime < 5) modifiedProductionTime = 5;
return modifiedProductionTime;
}
@@ -793,7 +719,7 @@ public class HideoutHelper(
PmcData pmcData)
{
var filterDrainRate = GetWaterFilterDrainRate(pmcData);
var craftProductionTime = GetTotalProductionTimeSeconds(HideoutHelper.WaterCollector);
var craftProductionTime = GetTotalProductionTimeSeconds(WaterCollector);
var secondsSinceServerTick = GetTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn);
filterDrainRate = GetTimeAdjustedWaterFilterDrainRate(
@@ -808,19 +734,14 @@ public class HideoutHelper(
// Check progress against the productions craft time (dont use base time as it doesnt include any time bonuses profile has)
if (production.Progress > production.ProductionTime)
{
// Craft is complete nothing to do
return;
}
// Check all slots that take water filters until we find one with filter in it
for (var i = 0; i < waterFilterArea.Slots.Count; i++)
{
// No water filter in slot, skip
if (waterFilterArea.Slots[i].Items is null)
{
continue;
}
if (waterFilterArea.Slots[i].Items is null) continue;
var waterFilterItemInSlot = waterFilterArea.Slots[i].Items[0];
@@ -841,7 +762,7 @@ public class HideoutHelper(
}
// Round to get values to 3dp
resourceValue = Math.Round((resourceValue * 1000) ?? 0) / 1000;
resourceValue = Math.Round(resourceValue * 1000 ?? 0) / 1000;
pointsConsumed = Math.Round(pointsConsumed * 1000) / 1000;
// Check units consumed for possible increment of hideout mgmt skill point
@@ -863,10 +784,7 @@ public class HideoutHelper(
pointsConsumed,
isWaterFilterFoundInRaid
);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Water filter has: {resourceValue} units left in slot {i + 1}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Water filter has: {resourceValue} units left in slot {i + 1}");
break; // Break here to avoid iterating other filters now w're done
}
@@ -942,15 +860,13 @@ public class HideoutHelper(
/// <returns>Seconds to produce item</returns>
protected double GetTotalProductionTimeSeconds(string prodId)
{
return (
_databaseService.GetHideout()
.Production.Recipes.FirstOrDefault(
(prod) =>
prod.Id == prodId
)
?.ProductionTime ??
0
);
return _databaseService.GetHideout()
.Production.Recipes.FirstOrDefault(
(prod) =>
prod.Id == prodId
)
?.ProductionTime ??
0;
}
/// <summary>
@@ -970,7 +886,7 @@ public class HideoutHelper(
{
StackObjectsCount = stackCount,
Resource = new UpdResource { Value = resourceValue, UnitsConsumed = resourceUnitsConsumed },
SpawnedInSession = isFoundInRaid,
SpawnedInSession = isFoundInRaid
};
}
@@ -991,7 +907,6 @@ public class HideoutHelper(
var pointsConsumed = 0D;
for (var i = 0; i < airFilterArea.Slots.Count; i++)
{
if (airFilterArea.Slots[i].Items is not null)
{
var resourceValue = airFilterArea.Slots[i].Items[0].Upd?.Resource is not null
@@ -1005,11 +920,11 @@ public class HideoutHelper(
}
else
{
pointsConsumed = ((airFilterArea.Slots[i].Items[0].Upd.Resource.UnitsConsumed ?? 0) + filterDrainRate) ?? 0;
pointsConsumed = (airFilterArea.Slots[i].Items[0].Upd.Resource.UnitsConsumed ?? 0) + filterDrainRate ?? 0;
resourceValue -= filterDrainRate;
}
resourceValue = Math.Round((resourceValue * 10000) ?? 0) / 10000;
resourceValue = Math.Round(resourceValue * 10000 ?? 0) / 10000;
pointsConsumed = Math.Round(pointsConsumed * 10000) / 10000;
// check unit consumed for increment skill point
@@ -1024,12 +939,9 @@ public class HideoutHelper(
airFilterArea.Slots[i].Items[0].Upd = new Upd
{
StackObjectsCount = 1,
Resource = new UpdResource { Value = resourceValue, UnitsConsumed = pointsConsumed },
Resource = new UpdResource { Value = resourceValue, UnitsConsumed = pointsConsumed }
};
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Air filter: {resourceValue} filter left on slot {i + 1}");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Air filter: {resourceValue} filter left on slot {i + 1}");
break; // Break here to avoid updating all filters
}
@@ -1037,7 +949,6 @@ public class HideoutHelper(
// Update remaining resources to be subtracted
filterDrainRate = Math.Abs(resourceValue ?? 0);
}
}
}
protected void UpdateBitcoinFarm(
@@ -1047,10 +958,7 @@ public class HideoutHelper(
bool isGeneratorOn)
{
var isBtcProd = btcProduction.GetType() == typeof(Production);
if (!isBtcProd)
{
return;
}
if (!isBtcProd) return;
// The wiki has a wrong formula!
// Do not change unless you validate it with the Client code files!
@@ -1088,10 +996,8 @@ public class HideoutHelper(
*/
// Needs power to function
if (!isGeneratorOn)
{
// Return with no changes
return;
}
var coinSlotCount = GetBTCSlots(pmcData);
@@ -1106,7 +1012,7 @@ public class HideoutHelper(
var bitcoinProdData = _databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault((production) => production.Id == HideoutHelper.BitcoinProductionId);
.Production.Recipes.FirstOrDefault((production) => production.Id == BitcoinProductionId);
// BSG finally fixed their settings, they now get loaded from the settings and used in the client
var adjustedCraftTime =
@@ -1116,21 +1022,15 @@ public class HideoutHelper(
// The progress should be adjusted based on the GPU boost rate, but the target is still the base productionTime
var timeMultiplier = bitcoinProdData.ProductionTime / adjustedCraftTime;
var timeElapsedSeconds = GetTimeElapsedSinceLastServerTick(pmcData, isGeneratorOn);
btcProduction.Progress += Math.Floor((timeElapsedSeconds * timeMultiplier) ?? 0);
btcProduction.Progress += Math.Floor(timeElapsedSeconds * timeMultiplier ?? 0);
while (btcProduction.Progress >= bitcoinProdData.ProductionTime)
{
if (btcProduction.Products.Count < coinSlotCount)
{
// Has space to add a coin to production rewards
AddBtcToProduction(btcProduction, bitcoinProdData.ProductionTime ?? 0);
}
else
{
// Filled up bitcoin storage
btcProduction.Progress = 0;
}
}
btcProduction.StartTimestamp = _timeUtil.GetTimeStamp();
}
@@ -1147,7 +1047,7 @@ public class HideoutHelper(
{
Id = _hashUtil.Generate(),
Template = ItemTpl.BARTER_PHYSICAL_BITCOIN,
Upd = new Upd { StackObjectsCount = 1 },
Upd = new Upd { StackObjectsCount = 1 }
}
);
@@ -1170,17 +1070,15 @@ public class HideoutHelper(
// Reduce time elapsed (and progress) when generator is off
var timeElapsed = _timeUtil.GetTimeStamp() - pmcData.Hideout.SptUpdateLastRunTimestamp;
if (recipe is not null) {
if (recipe is not null)
{
var hideoutArea = _databaseService.GetHideout().Areas.FirstOrDefault((area) => area.Type == recipe.AreaType);
if (!(hideoutArea.NeedsFuel ?? false)) {
if (!(hideoutArea.NeedsFuel ?? false))
// e.g. Lavatory works at 100% when power is on / off
return timeElapsed;
}
}
if (!isGeneratorOn) {
timeElapsed *= (long)_databaseService.GetHideout().Settings.GeneratorSpeedWithoutFuel;
}
if (!isGeneratorOn) timeElapsed *= (long)_databaseService.GetHideout().Settings.GeneratorSpeedWithoutFuel;
return timeElapsed;
}
@@ -1194,7 +1092,7 @@ public class HideoutHelper(
{
var bitcoinProductions = _databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault((production) => production.Id == HideoutHelper.BitcoinFarm);
.Production.Recipes.FirstOrDefault((production) => production.Id == BitcoinFarm);
var productionSlots = bitcoinProductions?.ProductionLimitCount ?? 3; // Default to 3 if none found
var hasManagementSkillSlots = _profileHelper.HasEliteSkillLevel(SkillTypes.HideoutManagement, pmcData);
var managementSlotsCount = GetEliteSkillAdditionalBitcoinSlotCount() ?? 2;
@@ -1207,7 +1105,8 @@ public class HideoutHelper(
/// </summary>
protected double? GetEliteSkillAdditionalBitcoinSlotCount()
{
return _databaseService.GetGlobals().Configuration.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm
return _databaseService.GetGlobals()
.Configuration.SkillsSettings.HideoutManagement.EliteSlots.BitcoinFarm
.Container;
}
@@ -1220,22 +1119,19 @@ public class HideoutHelper(
protected double? GetHideoutManagementConsumptionBonus(PmcData pmcData)
{
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.HideoutManagement);
if (hideoutManagementSkill is null || hideoutManagementSkill.Progress == 0) {
return 0;
}
if (hideoutManagementSkill is null || hideoutManagementSkill.Progress == 0) return 0;
// If the level is 51 we need to round it at 50 so on elite you dont get 25.5%
// at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki
// says that it caps at level 51 with 25% but as per dump data that is incorrect apparently
var roundedLevel = Math.Floor((hideoutManagementSkill.Progress / 100) ?? 0D);
var roundedLevel = Math.Floor(hideoutManagementSkill.Progress / 100 ?? 0D);
roundedLevel = roundedLevel == 51 ? roundedLevel - 1 : roundedLevel;
return (
(roundedLevel *
_databaseService.GetGlobals().Configuration.SkillsSettings.HideoutManagement
.ConsumptionReductionPerLevel) /
100
);
return roundedLevel *
_databaseService.GetGlobals()
.Configuration.SkillsSettings.HideoutManagement
.ConsumptionReductionPerLevel /
100;
}
/// <summary>
@@ -1248,17 +1144,15 @@ public class HideoutHelper(
protected double GetSkillBonusMultipliedBySkillLevel(PmcData pmcData, SkillTypes skill, double valuePerLevel)
{
var profileSkill = _profileHelper.GetSkillFromProfile(pmcData, skill);
if (profileSkill is null || profileSkill.Progress == 0) {
return 0;
}
if (profileSkill is null || profileSkill.Progress == 0) return 0;
// If the level is 51 we need to round it at 50 so on elite you dont get 25.5%
// at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki
// says that it caps at level 51 with 25% but as per dump data that is incorrect apparently
var roundedLevel = Math.Floor((profileSkill.Progress / 100) ?? 0D);
var roundedLevel = Math.Floor(profileSkill.Progress / 100 ?? 0D);
roundedLevel = roundedLevel == 51 ? roundedLevel - 1 : roundedLevel;
return (roundedLevel * valuePerLevel) / 100;
return roundedLevel * valuePerLevel / 100;
}
/// <summary>
@@ -1294,8 +1188,9 @@ public class HideoutHelper(
ItemEventRouterResponse output)
{
// Get how many coins were crafted and ready to pick up
var craftedCoinCount = pmcData.Hideout.Production[HideoutHelper.BitcoinFarm]?.Products?.Count;
if (craftedCoinCount is null) {
var craftedCoinCount = pmcData.Hideout.Production[BitcoinFarm]?.Products?.Count;
if (craftedCoinCount is null)
{
var errorMsg = _localisationService.GetText("hideout-no_bitcoins_to_collect");
_logger.Error(errorMsg);
@@ -1305,41 +1200,41 @@ public class HideoutHelper(
}
List<List<Item>> itemsToAdd = [];
for (var index = 0; index < craftedCoinCount; index++) {
itemsToAdd.Add([new Item
{
Id = _hashUtil.Generate(),
Template = ItemTpl.BARTER_PHYSICAL_BITCOIN,
Upd = new Upd { StackObjectsCount = 1 },
},
]);
}
for (var index = 0; index < craftedCoinCount; index++)
itemsToAdd.Add(
[
new Item
{
Id = _hashUtil.Generate(),
Template = ItemTpl.BARTER_PHYSICAL_BITCOIN,
Upd = new Upd { StackObjectsCount = 1 }
}
]
);
// Create request for what we want to add to stash
AddItemsDirectRequest addItemsRequest = new AddItemsDirectRequest {
var addItemsRequest = new AddItemsDirectRequest
{
ItemsWithModsToAdd = itemsToAdd,
FoundInRaid = true,
UseSortingTable = false,
Callback = null,
Callback = null
};
// Add FiR coins to player inventory
_inventoryHelper.AddItemsToStash(sessionId, addItemsRequest, pmcData, output);
if (output.Warnings?.Count > 0) {
return;
}
if (output.Warnings?.Count > 0) return;
// Is at max capacity + we collected all coins - reset production start time
var coinSlotCount = GetBTCSlots(pmcData);
if (pmcData.Hideout.Production[HideoutHelper.BitcoinFarm].Products.Count >= coinSlotCount) {
if (pmcData.Hideout.Production[BitcoinFarm].Products.Count >= coinSlotCount)
// Set start to now
pmcData.Hideout.Production[HideoutHelper.BitcoinFarm].StartTimestamp = _timeUtil
pmcData.Hideout.Production[BitcoinFarm].StartTimestamp = _timeUtil
.GetTimeStamp();
}
// Remove crafted coins from production in profile now they've been collected
// Can only collect all coins, not individially
pmcData.Hideout.Production[HideoutHelper.BitcoinFarm].Products = [];
pmcData.Hideout.Production[BitcoinFarm].Products = [];
}
/// <summary>
@@ -1354,16 +1249,10 @@ public class HideoutHelper(
var wall = profileHideoutAreas.FirstOrDefault((x) => x.Type == HideoutAreas.EMERGENCY_WALL);
// No collector or med station, skip
if ((waterCollector is null && medStation is null))
{
return;
}
if (waterCollector is null && medStation is null) return;
// If med-station > level 1 AND water collector > level 1 AND wall is level 0
if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0)
{
wall.Level = 3;
}
if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0) wall.Level = 3;
}
/// <summary>
@@ -1384,16 +1273,11 @@ public class HideoutHelper(
{
foreach (var improvementId in profileData.Hideout.Improvements)
{
if (!profileData.Hideout.Improvements.TryGetValue(improvementId.Key, out var improvementDetails))
{
continue;
}
if (!profileData.Hideout.Improvements.TryGetValue(improvementId.Key, out var improvementDetails)) continue;
if (improvementDetails.Completed == false && improvementDetails.ImproveCompleteTimestamp < _timeUtil.GetTimeStamp()
)
{
improvementDetails.Completed = true;
}
}
}
@@ -1411,9 +1295,10 @@ public class HideoutHelper(
.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.PLACE_OF_FAME);
// Get SkillGroupLevelingBoost object
var combatBoostBonusDb = fameAreaDb.Stages[fameAreaProfile.Level.ToString()].Bonuses.FirstOrDefault(
(bonus) => bonus.Type.ToString() == "SkillGroupLevelingBoost"
);
var combatBoostBonusDb = fameAreaDb.Stages[fameAreaProfile.Level.ToString()]
.Bonuses.FirstOrDefault(
(bonus) => bonus.Type.ToString() == "SkillGroupLevelingBoost"
);
// Get SkillGroupLevelingBoost object in profile
var combatBonusProfile = pmcData.Bonuses.FirstOrDefault((bonus) => bonus.Id == combatBoostBonusDb.Id);
@@ -1425,10 +1310,10 @@ public class HideoutHelper(
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.HideoutManagement);
var hideoutManagementSkillBonusPercent = 1 + hideoutManagementSkill.Progress / 10000; // 5100 becomes 0.51, add 1 to it, 1.51
var bonus =
GetDogtagCombatSkillBonusPercent(pmcData, activeDogtags) * hideoutManagementSkillBonusPercent;
GetDogtagCombatSkillBonusPercent(pmcData, activeDogtags) * hideoutManagementSkillBonusPercent;
// Update bonus value to above calcualted value
combatBonusProfile.Value = Math.Round((bonus ?? 0), 2);
combatBonusProfile.Value = Math.Round(bonus ?? 0, 2);
}
/// <summary>
@@ -1443,16 +1328,13 @@ public class HideoutHelper(
// Not own dogtag
// Side = opposite of player
var result = 0D;
foreach (var dogtag in activeDogtags) {
if (dogtag.Upd.Dogtag is null) {
continue;
}
foreach (var dogtag in activeDogtags)
{
if (dogtag.Upd.Dogtag is null) continue;
if (int.Parse(dogtag.Upd.Dogtag?.AccountId) == pmcData.Aid) {
continue;
}
if (int.Parse(dogtag.Upd.Dogtag?.AccountId) == pmcData.Aid) continue;
result += (0.01 * dogtag.Upd.Dogtag.Level) ?? 0;
result += 0.01 * dogtag.Upd.Dogtag.Level ?? 0;
}
return result;
@@ -1471,14 +1353,9 @@ public class HideoutHelper(
// Get all bonus Ids that the wall adds
List<string> bonusIdsToRemove = [];
foreach (var bonus in wallBonuses) {
bonusIdsToRemove.Add(bonus.Id);
}
foreach (var bonus in wallBonuses) bonusIdsToRemove.Add(bonus.Id);
if (_logger.IsLogEnabled(LogLevel.Debug))
{
_logger.Debug($"Removing: {bonusIdsToRemove.Count} bonuses from profile");
}
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Removing: {bonusIdsToRemove.Count} bonuses from profile");
// Remove the wall bonuses from profile by id
pmcData.Bonuses = pmcData.Bonuses.Where((bonus) => !bonusIdsToRemove.Contains(bonus.Id)).ToList();

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