.NET Format Style Fixes
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
using SPTarkov.Server.Core.Constants;
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Constants;
|
||||
using SPTarkov.Server.Core.Helpers;
|
||||
using SPTarkov.Server.Core.Models.Common;
|
||||
using SPTarkov.Server.Core.Models.Eft.Common;
|
||||
@@ -16,7 +16,6 @@ using BodyPart = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPart;
|
||||
using BodyParts = SPTarkov.Server.Core.Constants.BodyParts;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
namespace SPTarkov.Server.Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
@@ -50,7 +49,13 @@ public class BotGenerator(
|
||||
/// <param name="botTemplate">base bot template to use (e.g. assault/pmcbot)</param>
|
||||
/// <param name="profile">profile of player generating pscav</param>
|
||||
/// <returns>BotBase</returns>
|
||||
public PmcData GeneratePlayerScav(string sessionId, string role, string difficulty, BotType botTemplate, PmcData profile)
|
||||
public PmcData GeneratePlayerScav(
|
||||
string sessionId,
|
||||
string role,
|
||||
string difficulty,
|
||||
BotType botTemplate,
|
||||
PmcData profile
|
||||
)
|
||||
{
|
||||
var bot = GetCloneOfBotBase();
|
||||
bot.Info.Settings.BotDifficulty = difficulty;
|
||||
@@ -66,7 +71,7 @@ public class BotGenerator(
|
||||
BotRelativeLevelDeltaMin = 0,
|
||||
BotCountToGenerate = 1,
|
||||
BotDifficulty = difficulty,
|
||||
IsPlayerScav = true
|
||||
IsPlayerScav = true,
|
||||
};
|
||||
|
||||
bot = GenerateBot(sessionId, bot, botTemplate, botGenDetails);
|
||||
@@ -105,7 +110,7 @@ public class BotGenerator(
|
||||
WishList = bot.WishList,
|
||||
MoneyTransferLimitData = bot.MoneyTransferLimitData,
|
||||
IsPmc = bot.IsPmc,
|
||||
Prestige = new Dictionary<string, long>()
|
||||
Prestige = new Dictionary<string, long>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,7 +120,10 @@ public class BotGenerator(
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="botGenerationDetails">details on how to generate bots</param>
|
||||
/// <returns>constructed bot</returns>
|
||||
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails? botGenerationDetails)
|
||||
public BotBase PrepareAndGenerateBot(
|
||||
string sessionId,
|
||||
BotGenerationDetails? botGenerationDetails
|
||||
)
|
||||
{
|
||||
var preparedBotBase = GetPreparedBotBase(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided
|
||||
@@ -124,13 +132,16 @@ public class BotGenerator(
|
||||
);
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
var botRole = botGenerationDetails.IsPmc ?? false
|
||||
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
|
||||
: botGenerationDetails.Role;
|
||||
var botRole =
|
||||
botGenerationDetails.IsPmc ?? false
|
||||
? 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");
|
||||
_logger.Error(
|
||||
$"Unable to retrieve: {botRole} bot template, cannot generate bot of this type"
|
||||
);
|
||||
}
|
||||
|
||||
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
|
||||
@@ -174,7 +185,8 @@ public class BotGenerator(
|
||||
string sessionId,
|
||||
BotBase bot,
|
||||
BotType botJsonTemplate,
|
||||
BotGenerationDetails botGenerationDetails)
|
||||
BotGenerationDetails botGenerationDetails
|
||||
)
|
||||
{
|
||||
var botRoleLowercase = botGenerationDetails.Role.ToLower();
|
||||
var botLevel = _botLevelGenerator.GenerateBotLevel(
|
||||
@@ -207,14 +219,17 @@ public class BotGenerator(
|
||||
: string.Empty;
|
||||
|
||||
// Only run when generating a 'fake' playerscav, not actual player scav
|
||||
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && ShouldSimulatePlayerScav(botRoleLowercase))
|
||||
if (
|
||||
!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
|
||||
&& ShouldSimulatePlayerScav(botRoleLowercase)
|
||||
)
|
||||
{
|
||||
_botNameService.AddRandomPmcNameToBotMainProfileNicknameProperty(bot);
|
||||
SetRandomisedGameVersionAndCategory(bot.Info);
|
||||
}
|
||||
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
// Process all bots EXCEPT gifter, he needs christmas items
|
||||
// Process all bots EXCEPT gifter, he needs christmas items
|
||||
{
|
||||
if (botGenerationDetails.Role != "gifter")
|
||||
{
|
||||
@@ -228,7 +243,12 @@ public class BotGenerator(
|
||||
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)))
|
||||
if (
|
||||
!(
|
||||
botGenerationDetails.IsPmc.GetValueOrDefault(false)
|
||||
|| botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
|
||||
)
|
||||
)
|
||||
{
|
||||
bot.Hideout = null;
|
||||
}
|
||||
@@ -250,9 +270,15 @@ public class BotGenerator(
|
||||
botGenerationDetails.BotDifficulty,
|
||||
botGenerationDetails.Role
|
||||
);
|
||||
bot.Info.Settings.UseSimpleAnimator = botJsonTemplate.BotExperience.UseSimpleAnimator ?? false;
|
||||
bot.Info.Voice = _weightedRandomHelper.GetWeightedValue(botJsonTemplate.BotAppearance.Voice);
|
||||
bot.Health = GenerateHealth(botJsonTemplate.BotHealth, botGenerationDetails.IsPlayerScav.GetValueOrDefault(false));
|
||||
bot.Info.Settings.UseSimpleAnimator =
|
||||
botJsonTemplate.BotExperience.UseSimpleAnimator ?? false;
|
||||
bot.Info.Voice = _weightedRandomHelper.GetWeightedValue(
|
||||
botJsonTemplate.BotAppearance.Voice
|
||||
);
|
||||
bot.Health = GenerateHealth(
|
||||
botJsonTemplate.BotHealth,
|
||||
botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
|
||||
);
|
||||
bot.Skills = GenerateSkills(botJsonTemplate.BotSkills);
|
||||
bot.Info.PrestigeLevel = 0;
|
||||
|
||||
@@ -308,7 +334,8 @@ public class BotGenerator(
|
||||
/// <returns>True if name should be simulated pscav</returns>
|
||||
public bool ShouldSimulatePlayerScav(string botRole)
|
||||
{
|
||||
return botRole == Roles.Assault && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
return botRole == Roles.Assault
|
||||
&& _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -318,13 +345,19 @@ public class BotGenerator(
|
||||
/// <param name="botDifficulty">the killed bots difficulty</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Experience for kill</returns>
|
||||
public int GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax<int>> experiences, string botDifficulty, string role)
|
||||
public int GetExperienceRewardForKillByDifficulty(
|
||||
Dictionary<string, MinMax<int>> 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`");
|
||||
_logger.Debug(
|
||||
$"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`"
|
||||
);
|
||||
}
|
||||
|
||||
return _randomUtil.GetInt(experiences["normal"].Min, experiences["normal"].Max);
|
||||
@@ -332,7 +365,6 @@ public class BotGenerator(
|
||||
|
||||
// Some bots have -1/-1, shortcut result
|
||||
|
||||
|
||||
if (result.Max == -1)
|
||||
{
|
||||
return -1;
|
||||
@@ -348,11 +380,17 @@ public class BotGenerator(
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Standing change value</returns>
|
||||
public double GetStandingChangeForKillByDifficulty(Dictionary<string, double> standingsForKill, string botDifficulty, string role)
|
||||
public double GetStandingChangeForKillByDifficulty(
|
||||
Dictionary<string, double> standingsForKill,
|
||||
string botDifficulty,
|
||||
string role
|
||||
)
|
||||
{
|
||||
if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
_logger.Warning(
|
||||
$"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`"
|
||||
);
|
||||
|
||||
return standingsForKill["normal"];
|
||||
}
|
||||
@@ -367,11 +405,17 @@ public class BotGenerator(
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Standing change value</returns>
|
||||
public double GetAggressorBonusByDifficulty(Dictionary<string, double> aggressorBonuses, string botDifficulty, string role)
|
||||
public double GetAggressorBonusByDifficulty(
|
||||
Dictionary<string, double> aggressorBonuses,
|
||||
string botDifficulty,
|
||||
string role
|
||||
)
|
||||
{
|
||||
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
_logger.Warning(
|
||||
$"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`"
|
||||
);
|
||||
|
||||
return aggressorBonuses["normal"];
|
||||
}
|
||||
@@ -384,7 +428,10 @@ public class BotGenerator(
|
||||
/// </summary>
|
||||
/// <param name="botJsonTemplate">Bot data to adjust</param>
|
||||
/// <param name="botGenerationDetails">Generation details of bot</param>
|
||||
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
|
||||
public void FilterBlacklistedGear(
|
||||
BotType botJsonTemplate,
|
||||
BotGenerationDetails botGenerationDetails
|
||||
)
|
||||
{
|
||||
var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
|
||||
_botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role),
|
||||
@@ -392,7 +439,7 @@ public class BotGenerator(
|
||||
);
|
||||
|
||||
if (blacklist?.Gear is null)
|
||||
// Nothing to filter by
|
||||
// Nothing to filter by
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -402,7 +449,7 @@ public class BotGenerator(
|
||||
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentSlot];
|
||||
|
||||
foreach (var blacklistedTpl in blacklistedTpls)
|
||||
// Set weighting to 0, will never be picked
|
||||
// Set weighting to 0, will never be picked
|
||||
{
|
||||
equipmentDict[blacklistedTpl] = 0;
|
||||
}
|
||||
@@ -433,9 +480,10 @@ public class BotGenerator(
|
||||
// Remove blacklisted loot from loot containers
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var propInfo = props
|
||||
.FirstOrDefault(x => string.Equals(x.Name, lootContainerKey, StringComparison.CurrentCultureIgnoreCase));
|
||||
var prop = (Dictionary<string, double>?) propInfo.GetValue(botInventory.Items);
|
||||
var propInfo = props.FirstOrDefault(x =>
|
||||
string.Equals(x.Name, lootContainerKey, StringComparison.CurrentCultureIgnoreCase)
|
||||
);
|
||||
var prop = (Dictionary<string, double>?)propInfo.GetValue(botInventory.Items);
|
||||
|
||||
// No container, skip
|
||||
if (prop is null)
|
||||
@@ -444,9 +492,10 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
var newProp = prop.Where(tpl =>
|
||||
{
|
||||
return !_itemFilterService.IsLootableItemBlacklisted(tpl.Key);
|
||||
}).ToDictionary();
|
||||
{
|
||||
return !_itemFilterService.IsLootableItemBlacklisted(tpl.Key);
|
||||
})
|
||||
.ToDictionary();
|
||||
propInfo.SetValue(botInventory.Items, newProp);
|
||||
}
|
||||
}
|
||||
@@ -457,7 +506,11 @@ public class BotGenerator(
|
||||
/// <param name="bot">Bot to adjust</param>
|
||||
/// <param name="appearance">Appearance settings to choose from</param>
|
||||
/// <param name="botGenerationDetails">Generation details</param>
|
||||
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails)
|
||||
public void SetBotAppearance(
|
||||
BotBase bot,
|
||||
Appearance appearance,
|
||||
BotGenerationDetails botGenerationDetails
|
||||
)
|
||||
{
|
||||
// Choose random values by weight
|
||||
bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head);
|
||||
@@ -468,10 +521,13 @@ public class BotGenerator(
|
||||
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
|
||||
|
||||
// Some bodies have matching hands, look up body to see if this is the case
|
||||
var chosenBody = bodyGlobalDictDb.FirstOrDefault(c => c.Key == chosenBodyTemplate?.Name.Trim());
|
||||
bot.Customization.Hands = chosenBody.Value?.IsNotRandom ?? false
|
||||
? chosenBody.Value.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
var chosenBody = bodyGlobalDictDb.FirstOrDefault(c =>
|
||||
c.Key == chosenBodyTemplate?.Name.Trim()
|
||||
);
|
||||
bot.Customization.Hands =
|
||||
chosenBody.Value?.IsNotRandom ?? false
|
||||
? chosenBody.Value.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -491,93 +547,121 @@ public class BotGenerator(
|
||||
Hydration = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(healthObj.Hydration.Min, healthObj.Hydration.Max),
|
||||
Maximum = healthObj.Hydration.Max
|
||||
Maximum = healthObj.Hydration.Max,
|
||||
},
|
||||
Energy = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(healthObj.Energy.Min, healthObj.Energy.Max),
|
||||
Maximum = healthObj.Energy.Max
|
||||
Maximum = healthObj.Energy.Max,
|
||||
},
|
||||
Temperature = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(healthObj.Temperature.Min, healthObj.Temperature.Max),
|
||||
Maximum = healthObj.Temperature.Max
|
||||
Current = _randomUtil.GetDouble(
|
||||
healthObj.Temperature.Min,
|
||||
healthObj.Temperature.Max
|
||||
),
|
||||
Maximum = healthObj.Temperature.Max,
|
||||
},
|
||||
BodyParts = new Dictionary<string, BodyPartHealth>
|
||||
{
|
||||
{
|
||||
BodyParts.Head, new BodyPartHealth
|
||||
BodyParts.Head,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.Head.Min, bodyParts.Head.Max),
|
||||
Maximum = Math.Round(bodyParts.Head.Max)
|
||||
}
|
||||
Maximum = Math.Round(bodyParts.Head.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.Chest, new BodyPartHealth
|
||||
BodyParts.Chest,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.Chest.Min, bodyParts.Chest.Max),
|
||||
Maximum = Math.Round(bodyParts.Chest.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.Chest.Min,
|
||||
bodyParts.Chest.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.Chest.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.Stomach, new BodyPartHealth
|
||||
BodyParts.Stomach,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.Stomach.Min, bodyParts.Stomach.Max),
|
||||
Maximum = Math.Round(bodyParts.Stomach.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.Stomach.Min,
|
||||
bodyParts.Stomach.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.Stomach.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.LeftArm, new BodyPartHealth
|
||||
BodyParts.LeftArm,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.LeftArm.Min, bodyParts.LeftArm.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftArm.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.LeftArm.Min,
|
||||
bodyParts.LeftArm.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.LeftArm.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.RightArm, new BodyPartHealth
|
||||
BodyParts.RightArm,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.RightArm.Min, bodyParts.RightArm.Max),
|
||||
Maximum = Math.Round(bodyParts.RightArm.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.RightArm.Min,
|
||||
bodyParts.RightArm.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.RightArm.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.LeftLeg, new BodyPartHealth
|
||||
BodyParts.LeftLeg,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.LeftLeg.Min, bodyParts.LeftLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftLeg.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.LeftLeg.Min,
|
||||
bodyParts.LeftLeg.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.LeftLeg.Max),
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
BodyParts.RightLeg, new BodyPartHealth
|
||||
BodyParts.RightLeg,
|
||||
new BodyPartHealth
|
||||
{
|
||||
Health = new CurrentMinMax
|
||||
{
|
||||
Current = _randomUtil.GetDouble(bodyParts.RightLeg.Min, bodyParts.RightLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.RightLeg.Max)
|
||||
}
|
||||
Current = _randomUtil.GetDouble(
|
||||
bodyParts.RightLeg.Min,
|
||||
bodyParts.RightLeg.Max
|
||||
),
|
||||
Maximum = Math.Round(bodyParts.RightLeg.Max),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
UpdateTime = 0, // 0 for player-scav too
|
||||
Immortal = false
|
||||
Immortal = false,
|
||||
};
|
||||
|
||||
return health;
|
||||
@@ -602,9 +686,13 @@ public class BotGenerator(
|
||||
{
|
||||
double? hpTotal = 0;
|
||||
|
||||
foreach (var prop in props.Where(property => !property.Name.Equals("extensiondata", StringComparison.OrdinalIgnoreCase)))
|
||||
foreach (
|
||||
var prop in props.Where(property =>
|
||||
!property.Name.Equals("extensiondata", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
)
|
||||
{
|
||||
var value = (MinMax<double>) prop.GetValue(bodyPart);
|
||||
var value = (MinMax<double>)prop.GetValue(bodyPart);
|
||||
hpTotal += value.Max;
|
||||
}
|
||||
|
||||
@@ -630,7 +718,7 @@ public class BotGenerator(
|
||||
{
|
||||
Common = GetCommonSkillsWithRandomisedProgressValue(botSkills.Common),
|
||||
Mastering = GetMasteringSkillsWithRandomisedProgressValue(botSkills.Mastering),
|
||||
Points = 0
|
||||
Points = 0,
|
||||
};
|
||||
|
||||
return skillsToReturn;
|
||||
@@ -641,7 +729,9 @@ public class BotGenerator(
|
||||
/// </summary>
|
||||
/// <param name="skills">Skills to randomise</param>
|
||||
/// <returns>Skills with randomised progress values as a collection</returns>
|
||||
public List<CommonSkill> GetCommonSkillsWithRandomisedProgressValue(Dictionary<string, MinMax<double>>? skills)
|
||||
public List<CommonSkill> GetCommonSkillsWithRandomisedProgressValue(
|
||||
Dictionary<string, MinMax<double>>? skills
|
||||
)
|
||||
{
|
||||
if (skills is null)
|
||||
{
|
||||
@@ -650,23 +740,22 @@ public class BotGenerator(
|
||||
|
||||
return skills
|
||||
.Select(kvp =>
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CommonSkill
|
||||
{
|
||||
Id = Enum.Parse<SkillTypes>(kvp.Key),
|
||||
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
|
||||
PointsEarnedDuringSession = 0,
|
||||
LastAccess = 0
|
||||
};
|
||||
return null;
|
||||
}
|
||||
)
|
||||
|
||||
return new CommonSkill
|
||||
{
|
||||
Id = Enum.Parse<SkillTypes>(kvp.Key),
|
||||
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
|
||||
PointsEarnedDuringSession = 0,
|
||||
LastAccess = 0,
|
||||
};
|
||||
})
|
||||
.Where(baseSkill => baseSkill != null)
|
||||
.ToList();
|
||||
}
|
||||
@@ -676,7 +765,9 @@ public class BotGenerator(
|
||||
/// </summary>
|
||||
/// <param name="skills">Skills to randomise</param>
|
||||
/// <returns>Skills with randomised progress values as a collection</returns>
|
||||
public List<MasterySkill> GetMasteringSkillsWithRandomisedProgressValue(Dictionary<string, MinMax<double>>? skills)
|
||||
public List<MasterySkill> GetMasteringSkillsWithRandomisedProgressValue(
|
||||
Dictionary<string, MinMax<double>>? skills
|
||||
)
|
||||
{
|
||||
if (skills is null)
|
||||
{
|
||||
@@ -685,22 +776,21 @@ public class BotGenerator(
|
||||
|
||||
return skills
|
||||
.Select(kvp =>
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All skills have id and progress props
|
||||
return new MasterySkill
|
||||
{
|
||||
Id = kvp.Key,
|
||||
Progress = _randomUtil.GetDouble(skill.Min, skill.Max)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
)
|
||||
|
||||
// All skills have id and progress props
|
||||
return new MasterySkill
|
||||
{
|
||||
Id = kvp.Key,
|
||||
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
|
||||
};
|
||||
})
|
||||
.Where(baseSkill => baseSkill != null)
|
||||
.ToList();
|
||||
}
|
||||
@@ -716,7 +806,9 @@ public class BotGenerator(
|
||||
var botId = _hashUtil.Generate();
|
||||
|
||||
bot.Id = botId;
|
||||
bot.Aid = botGenerationDetails.IsPmc.GetValueOrDefault(false) ? _hashUtil.GenerateAccountId() : 0;
|
||||
bot.Aid = botGenerationDetails.IsPmc.GetValueOrDefault(false)
|
||||
? _hashUtil.GenerateAccountId()
|
||||
: 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -788,7 +880,9 @@ public class BotGenerator(
|
||||
break;
|
||||
default:
|
||||
// Everyone else gets a weighted randomised category
|
||||
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue(_pmcConfig.AccountTypeWeight);
|
||||
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue(
|
||||
_pmcConfig.AccountTypeWeight
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -811,10 +905,7 @@ public class BotGenerator(
|
||||
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
|
||||
ParentId = bot.Inventory.Equipment,
|
||||
SlotId = Slots.Dogtag,
|
||||
Upd = new Upd
|
||||
{
|
||||
SpawnedInSession = true
|
||||
}
|
||||
Upd = new Upd { SpawnedInSession = true },
|
||||
};
|
||||
|
||||
bot.Inventory.Items.Add(inventoryItem);
|
||||
|
||||
@@ -47,12 +47,16 @@ public class BotInventoryGenerator(
|
||||
EquipmentSlots.TacticalVest,
|
||||
EquipmentSlots.FaceCover,
|
||||
EquipmentSlots.Headwear,
|
||||
EquipmentSlots.Earpiece
|
||||
EquipmentSlots.Earpiece,
|
||||
];
|
||||
|
||||
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
|
||||
private readonly HashSet<string> _slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()];
|
||||
private readonly HashSet<string> _slotsToCheck =
|
||||
[
|
||||
EquipmentSlots.Pockets.ToString(),
|
||||
EquipmentSlots.SecuredContainer.ToString(),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Add equipment/weapons/loot to bot
|
||||
@@ -64,7 +68,14 @@ public class BotInventoryGenerator(
|
||||
/// <param name="botLevel">Level of bot being generated</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <returns>PmcInventory object with equipment/weapons/loot</returns>
|
||||
public BotBaseInventory GenerateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion)
|
||||
public BotBaseInventory GenerateInventory(
|
||||
string sessionId,
|
||||
BotType botJsonTemplate,
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
int botLevel,
|
||||
string chosenGameVersion
|
||||
)
|
||||
{
|
||||
var templateInventory = botJsonTemplate.BotInventory;
|
||||
var wornItemChances = botJsonTemplate.BotChances;
|
||||
@@ -74,7 +85,9 @@ public class BotInventoryGenerator(
|
||||
var botInventory = GenerateInventoryBase();
|
||||
|
||||
// Get generated raid details bot will be spawned in
|
||||
var raidConfig = _profileActivityService.GetProfileActivityRaidData(sessionId)?.RaidConfiguration;
|
||||
var raidConfig = _profileActivityService
|
||||
.GetProfileActivityRaidData(sessionId)
|
||||
?.RaidConfiguration;
|
||||
|
||||
GenerateAndAddEquipmentToBot(
|
||||
sessionId,
|
||||
@@ -101,7 +114,14 @@ public class BotInventoryGenerator(
|
||||
);
|
||||
|
||||
// Pick loot and add to bots containers (rig/backpack/pockets/secure)
|
||||
_botLootGenerator.GenerateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel);
|
||||
_botLootGenerator.GenerateLoot(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
isPmc,
|
||||
botRole,
|
||||
botInventory,
|
||||
botLevel
|
||||
);
|
||||
|
||||
return botInventory;
|
||||
}
|
||||
@@ -123,36 +143,16 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Items =
|
||||
[
|
||||
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 = 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
|
||||
}
|
||||
Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION,
|
||||
},
|
||||
],
|
||||
Equipment = equipmentId,
|
||||
Stash = stashId,
|
||||
@@ -162,7 +162,7 @@ public class BotInventoryGenerator(
|
||||
HideoutAreaStashes = new Dictionary<string, string>(),
|
||||
FastPanel = new Dictionary<string, string>(),
|
||||
FavoriteItems = [],
|
||||
HideoutCustomizationStashId = hideoutCustomizationStashId
|
||||
HideoutCustomizationStashId = hideoutCustomizationStashId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,24 +177,42 @@ public class BotInventoryGenerator(
|
||||
/// <param name="botLevel">Level of bot</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <param name="raidConfig">RadiConfig</param>
|
||||
public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole,
|
||||
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, bool isPmc, GetRaidConfigurationRequestData? raidConfig)
|
||||
public void GenerateAndAddEquipmentToBot(
|
||||
string sessionId,
|
||||
BotTypeInventory templateInventory,
|
||||
Chances wornItemChances,
|
||||
string botRole,
|
||||
BotBaseInventory botInventory,
|
||||
int botLevel,
|
||||
string chosenGameVersion,
|
||||
bool isPmc,
|
||||
GetRaidConfigurationRequestData? raidConfig
|
||||
)
|
||||
{
|
||||
_botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig);
|
||||
_botConfig.Equipment.TryGetValue(
|
||||
_botGeneratorHelper.GetBotEquipmentRole(botRole),
|
||||
out var botEquipConfig
|
||||
);
|
||||
var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig);
|
||||
|
||||
// Apply nighttime changes if its nighttime + there's changes to make
|
||||
if (
|
||||
randomistionDetails?.NighttimeChanges is not null &&
|
||||
raidConfig is not null &&
|
||||
_weatherHelper.IsNightTime(raidConfig.TimeVariant, raidConfig.Location)
|
||||
randomistionDetails?.NighttimeChanges is not null
|
||||
&& raidConfig is not null
|
||||
&& _weatherHelper.IsNightTime(raidConfig.TimeVariant, raidConfig.Location)
|
||||
)
|
||||
{
|
||||
foreach (var equipmentSlotKvP in randomistionDetails.NighttimeChanges.EquipmentModsModifiers)
|
||||
// Never let mod chance go outside 0 - 100
|
||||
foreach (
|
||||
var equipmentSlotKvP in randomistionDetails.NighttimeChanges.EquipmentModsModifiers
|
||||
)
|
||||
// Never let mod chance go outside 0 - 100
|
||||
{
|
||||
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
|
||||
Math.Max(randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0),
|
||||
Math.Max(
|
||||
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key]
|
||||
+ equipmentSlotKvP.Value,
|
||||
0
|
||||
),
|
||||
100
|
||||
);
|
||||
}
|
||||
@@ -204,7 +222,6 @@ public class BotInventoryGenerator(
|
||||
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
|
||||
var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole);
|
||||
|
||||
|
||||
// Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts
|
||||
// e.g. ArmorVest should be generated after TactivalVest
|
||||
// or FACE_COVER before HEADWEAR
|
||||
@@ -228,12 +245,12 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -244,20 +261,24 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Pockets,
|
||||
// Unheard profiles have unique sized pockets
|
||||
RootEquipmentPool = GetPocketPoolByGameEdition(chosenGameVersion, templateInventory, isPmc),
|
||||
RootEquipmentPool = GetPocketPoolByGameEdition(
|
||||
chosenGameVersion,
|
||||
templateInventory,
|
||||
isPmc
|
||||
),
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE, ItemTpl.POCKETS_LARGE],
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -272,12 +293,12 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -292,12 +313,12 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -312,12 +333,12 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -332,25 +353,25 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
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
|
||||
// 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
|
||||
// Filter rigs down to only those with armor
|
||||
{
|
||||
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
@@ -372,12 +393,12 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1
|
||||
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -389,13 +410,14 @@ public class BotInventoryGenerator(
|
||||
/// <param name="templateInventory"></param>
|
||||
/// <param name="isPmc">is bot a PMC</param>
|
||||
/// <returns></returns>
|
||||
protected Dictionary<string, double> GetPocketPoolByGameEdition(string chosenGameVersion, BotTypeInventory templateInventory, bool isPmc)
|
||||
protected Dictionary<string, double> GetPocketPoolByGameEdition(
|
||||
string chosenGameVersion,
|
||||
BotTypeInventory templateInventory,
|
||||
bool isPmc
|
||||
)
|
||||
{
|
||||
return chosenGameVersion == GameEditions.UNHEARD && isPmc
|
||||
? new Dictionary<string, double>
|
||||
{
|
||||
[ItemTpl.POCKETS_1X4_TUE] = 1
|
||||
}
|
||||
? new Dictionary<string, double> { [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
: templateInventory.Equipment.GetValueOrDefault(EquipmentSlots.Pockets);
|
||||
}
|
||||
|
||||
@@ -404,7 +426,10 @@ public class BotInventoryGenerator(
|
||||
/// </summary>
|
||||
/// <param name="templateEquipment">Equipment to filter TacticalVest of</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole)
|
||||
public void FilterRigsToThoseWithProtection(
|
||||
Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest]
|
||||
.Where(kvp => _itemHelper.ItemHasSlots(kvp.Key))
|
||||
@@ -414,7 +439,9 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
|
||||
_logger.Debug(
|
||||
$"Unable to filter to only armored rigs as bot: {botRole} has none in pool"
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -429,8 +456,11 @@ public class BotInventoryGenerator(
|
||||
/// <param name="templateEquipment">Equipment to filter TacticalVest by</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
/// <param name="allowEmptyResult">Should the function return all rigs when 0 unarmored are found</param>
|
||||
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole,
|
||||
bool allowEmptyResult = true)
|
||||
public void FilterRigsToThoseWithoutProtection(
|
||||
Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment,
|
||||
string botRole,
|
||||
bool allowEmptyResult = true
|
||||
)
|
||||
{
|
||||
var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest]
|
||||
.Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key))
|
||||
@@ -440,7 +470,9 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
|
||||
_logger.Debug(
|
||||
$"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool"
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -458,7 +490,9 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
double? spawnChance = _slotsToCheck.Contains(settings.RootEquipmentSlot.ToString())
|
||||
? 100
|
||||
: settings.SpawnChances.EquipmentChances.GetValueOrDefault(settings.RootEquipmentSlot.ToString());
|
||||
: settings.SpawnChances.EquipmentChances.GetValueOrDefault(
|
||||
settings.RootEquipmentSlot.ToString()
|
||||
);
|
||||
|
||||
if (!spawnChance.HasValue)
|
||||
{
|
||||
@@ -489,12 +523,16 @@ public class BotInventoryGenerator(
|
||||
return false;
|
||||
}
|
||||
|
||||
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool);
|
||||
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(
|
||||
settings.RootEquipmentPool
|
||||
);
|
||||
var dbResult = _itemHelper.GetItem(chosenItemTpl);
|
||||
|
||||
if (!dbResult.Key)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl));
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-missing_item_template", chosenItemTpl)
|
||||
);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
|
||||
@@ -544,7 +582,10 @@ public class BotInventoryGenerator(
|
||||
Template = pickedItemDb.Id,
|
||||
ParentId = settings.Inventory.Equipment,
|
||||
SlotId = settings.RootEquipmentSlot.ToString(),
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(pickedItemDb, settings.BotData.Role)
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
|
||||
pickedItemDb,
|
||||
settings.BotData.Role
|
||||
),
|
||||
};
|
||||
|
||||
var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
|
||||
@@ -553,10 +594,14 @@ public class BotInventoryGenerator(
|
||||
);
|
||||
|
||||
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
|
||||
if (_botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole) &&
|
||||
settings.RandomisationDetails?.RandomisedArmorSlots != null &&
|
||||
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))
|
||||
// Filter out mods from relevant blacklist
|
||||
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,
|
||||
@@ -564,7 +609,9 @@ public class BotInventoryGenerator(
|
||||
);
|
||||
}
|
||||
|
||||
var itemIsOnGenerateModBlacklist = settings.GenerateModsBlacklist != null && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id);
|
||||
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)
|
||||
{
|
||||
@@ -595,7 +642,10 @@ public class BotInventoryGenerator(
|
||||
/// <param name="itemTpl">Item mod pool is being retrieved and filtered</param>
|
||||
/// <param name="equipmentBlacklist">Blacklist to filter mod pool with</param>
|
||||
/// <returns>Filtered pool of mods</returns>
|
||||
public Dictionary<string, HashSet<string>> GetFilteredDynamicModsForItem(string itemTpl, Dictionary<string, HashSet<string>> equipmentBlacklist)
|
||||
public Dictionary<string, HashSet<string>> GetFilteredDynamicModsForItem(
|
||||
string itemTpl,
|
||||
Dictionary<string, HashSet<string>> equipmentBlacklist
|
||||
)
|
||||
{
|
||||
var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl);
|
||||
foreach (var modSlot in modPool)
|
||||
@@ -605,14 +655,16 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
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");
|
||||
_logger.Warning(
|
||||
$"Filtering {modSlot.Key} pool resulting in 0 items, skipping filter"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -633,14 +685,24 @@ public class BotInventoryGenerator(
|
||||
/// <param name="isPmc">Is the bot being generated as a pmc</param>
|
||||
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
|
||||
/// <param name="botLevel">level of bot having weapon generated</param>
|
||||
public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
string botRole, bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel)
|
||||
public void GenerateAndAddWeaponsToBot(
|
||||
BotTypeInventory templateInventory,
|
||||
Chances equipmentChances,
|
||||
string sessionId,
|
||||
BotBaseInventory botInventory,
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
Generation itemGenerationLimitsMinMax,
|
||||
int botLevel
|
||||
)
|
||||
{
|
||||
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
|
||||
// Add weapon to bot if true and bot json has something to put into the slot
|
||||
{
|
||||
if (desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any())
|
||||
if (
|
||||
desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any()
|
||||
)
|
||||
{
|
||||
AddWeaponAndMagazinesToInventory(
|
||||
sessionId,
|
||||
@@ -664,24 +726,32 @@ public class BotInventoryGenerator(
|
||||
/// <returns>What slots bot should have weapons generated for</returns>
|
||||
public List<DesiredWeapons> GetDesiredWeaponsForBot(Chances equipmentChances)
|
||||
{
|
||||
var shouldSpawnPrimary = _randomUtil.GetChance100(equipmentChances.EquipmentChances["FirstPrimaryWeapon"]);
|
||||
var shouldSpawnPrimary = _randomUtil.GetChance100(
|
||||
equipmentChances.EquipmentChances["FirstPrimaryWeapon"]
|
||||
);
|
||||
return
|
||||
[
|
||||
new DesiredWeapons
|
||||
{
|
||||
Slot = EquipmentSlots.FirstPrimaryWeapon,
|
||||
ShouldSpawn = shouldSpawnPrimary
|
||||
ShouldSpawn = shouldSpawnPrimary,
|
||||
},
|
||||
new DesiredWeapons
|
||||
{
|
||||
Slot = EquipmentSlots.SecondPrimaryWeapon,
|
||||
ShouldSpawn = shouldSpawnPrimary && _randomUtil.GetChance100(equipmentChances.EquipmentChances["SecondPrimaryWeapon"])
|
||||
ShouldSpawn =
|
||||
shouldSpawnPrimary
|
||||
&& _randomUtil.GetChance100(
|
||||
equipmentChances.EquipmentChances["SecondPrimaryWeapon"]
|
||||
),
|
||||
},
|
||||
new DesiredWeapons
|
||||
{
|
||||
Slot = EquipmentSlots.Holster,
|
||||
ShouldSpawn = !shouldSpawnPrimary || _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]) // No primary = force pistol
|
||||
}
|
||||
ShouldSpawn =
|
||||
!shouldSpawnPrimary
|
||||
|| _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]), // No primary = force pistol
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -697,9 +767,17 @@ public class BotInventoryGenerator(
|
||||
/// <param name="isPmc">Is the bot being generated as a pmc</param>
|
||||
/// <param name="itemGenerationWeights"></param>
|
||||
/// <param name="botLevel"></param>
|
||||
public void AddWeaponAndMagazinesToInventory(string sessionId, DesiredWeapons weaponSlot, BotTypeInventory templateInventory, BotBaseInventory botInventory,
|
||||
Chances equipmentChances, string botRole,
|
||||
bool isPmc, Generation itemGenerationWeights, int botLevel)
|
||||
public void AddWeaponAndMagazinesToInventory(
|
||||
string sessionId,
|
||||
DesiredWeapons weaponSlot,
|
||||
BotTypeInventory templateInventory,
|
||||
BotBaseInventory botInventory,
|
||||
Chances equipmentChances,
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
Generation itemGenerationWeights,
|
||||
int botLevel
|
||||
)
|
||||
{
|
||||
var generatedWeapon = _botWeaponGenerator.GenerateRandomWeapon(
|
||||
sessionId,
|
||||
@@ -725,15 +803,7 @@ public class BotInventoryGenerator(
|
||||
|
||||
public class DesiredWeapons
|
||||
{
|
||||
public EquipmentSlots Slot
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public EquipmentSlots Slot { get; set; }
|
||||
|
||||
public bool ShouldSpawn
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public bool ShouldSpawn { get; set; }
|
||||
}
|
||||
|
||||
@@ -24,25 +24,28 @@ public class BotLevelGenerator(
|
||||
/// <param name="botGenerationDetails">Details to help generate a bot</param>
|
||||
/// <param name="bot">Bot the level is being generated for</param>
|
||||
/// <returns>IRandomisedBotLevelResult object</returns>
|
||||
public RandomisedBotLevelResult GenerateBotLevel(MinMax<int> levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
|
||||
public RandomisedBotLevelResult GenerateBotLevel(
|
||||
MinMax<int> levelDetails,
|
||||
BotGenerationDetails botGenerationDetails,
|
||||
BotBase bot
|
||||
)
|
||||
{
|
||||
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false))
|
||||
{
|
||||
return new RandomisedBotLevelResult
|
||||
{
|
||||
Exp = 0,
|
||||
Level = 1
|
||||
};
|
||||
return new RandomisedBotLevelResult { Exp = 0, Level = 1 };
|
||||
}
|
||||
|
||||
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
|
||||
var botLevelRange = GetRelativePmcBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
|
||||
var botLevelRange = GetRelativePmcBotLevelRange(
|
||||
botGenerationDetails,
|
||||
levelDetails,
|
||||
expTable.Length
|
||||
);
|
||||
|
||||
// Get random level based on the exp table.
|
||||
var exp = 0;
|
||||
var level = int.Parse(
|
||||
ChooseBotLevel(botLevelRange.Min, botLevelRange.Max, 1, 1.15)
|
||||
.ToString()
|
||||
ChooseBotLevel(botLevelRange.Min, botLevelRange.Max, 1, 1.15).ToString()
|
||||
); // TODO - nasty double to string to int conversion
|
||||
for (var i = 0; i < level; i++)
|
||||
{
|
||||
@@ -55,11 +58,7 @@ public class BotLevelGenerator(
|
||||
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
|
||||
}
|
||||
|
||||
return new RandomisedBotLevelResult
|
||||
{
|
||||
Level = level,
|
||||
Exp = exp
|
||||
};
|
||||
return new RandomisedBotLevelResult { Level = level, Exp = exp };
|
||||
}
|
||||
|
||||
public double ChooseBotLevel(double min, double max, int shift, double number)
|
||||
@@ -74,7 +73,11 @@ public class BotLevelGenerator(
|
||||
/// <param name="levelDetails"></param>
|
||||
/// <param name="maxAvailableLevel">Max level allowed</param>
|
||||
/// <returns>A MinMax of the lowest and highest level to generate the bots</returns>
|
||||
public MinMax<int> GetRelativePmcBotLevelRange(BotGenerationDetails botGenerationDetails, MinMax<int> levelDetails, int maxAvailableLevel)
|
||||
public MinMax<int> GetRelativePmcBotLevelRange(
|
||||
BotGenerationDetails botGenerationDetails,
|
||||
MinMax<int> levelDetails,
|
||||
int maxAvailableLevel
|
||||
)
|
||||
{
|
||||
var levelOverride = botGenerationDetails.LocationSpecificPmcLevelOverride;
|
||||
|
||||
@@ -93,12 +96,14 @@ public class BotLevelGenerator(
|
||||
|
||||
// Get min level relative to player if value exists
|
||||
var minLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value - botGenerationDetails.BotRelativeLevelDeltaMin.Value
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
- botGenerationDetails.BotRelativeLevelDeltaMin.Value
|
||||
: 1 - botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
|
||||
// Get max level relative to player if value exists
|
||||
var maxLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value + botGenerationDetails.BotRelativeLevelDeltaMax.Value
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
+ botGenerationDetails.BotRelativeLevelDeltaMax.Value
|
||||
: 1 + botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
|
||||
// Bound the level to the min/max possible
|
||||
|
||||
@@ -54,7 +54,7 @@ public class BotLootGenerator(
|
||||
return new ItemSpawnLimitSettings
|
||||
{
|
||||
CurrentLimits = limitsForBotDict,
|
||||
GlobalLimits = GetItemSpawnLimitsForBotType(botRole)
|
||||
GlobalLimits = GetItemSpawnLimitsForBotType(botRole),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,33 +67,46 @@ public class BotLootGenerator(
|
||||
/// <param name="botRole">Role of bot, e.g. asssult</param>
|
||||
/// <param name="botInventory">Inventory to add loot to</param>
|
||||
/// <param name="botLevel">Level of bot</param>
|
||||
public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel)
|
||||
public void GenerateLoot(
|
||||
string sessionId,
|
||||
BotType botJsonTemplate,
|
||||
bool isPmc,
|
||||
string botRole,
|
||||
BotBaseInventory botInventory,
|
||||
int botLevel
|
||||
)
|
||||
{
|
||||
// Limits on item types to be added as loot
|
||||
var itemCounts = botJsonTemplate.BotGeneration?.Items;
|
||||
|
||||
if (
|
||||
itemCounts?.BackpackLoot.Weights is null ||
|
||||
itemCounts.PocketLoot.Weights is null ||
|
||||
itemCounts.VestLoot.Weights is null ||
|
||||
itemCounts.SpecialItems.Weights is null ||
|
||||
itemCounts.Healing.Weights is null ||
|
||||
itemCounts.Drugs.Weights is null ||
|
||||
itemCounts.Food.Weights is null ||
|
||||
itemCounts.Drink.Weights is null ||
|
||||
itemCounts.Currency.Weights is null ||
|
||||
itemCounts.Stims.Weights is null ||
|
||||
itemCounts.Grenades.Weights is null
|
||||
itemCounts?.BackpackLoot.Weights is null
|
||||
|| itemCounts.PocketLoot.Weights is null
|
||||
|| itemCounts.VestLoot.Weights is null
|
||||
|| itemCounts.SpecialItems.Weights is null
|
||||
|| itemCounts.Healing.Weights is null
|
||||
|| itemCounts.Drugs.Weights is null
|
||||
|| itemCounts.Food.Weights is null
|
||||
|| itemCounts.Drink.Weights is null
|
||||
|| itemCounts.Currency.Weights is null
|
||||
|| itemCounts.Stims.Weights is null
|
||||
|| itemCounts.Grenades.Weights is null
|
||||
)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.BackpackLoot.Weights);
|
||||
var backpackLootCount = _weightedRandomHelper.GetWeightedValue(
|
||||
itemCounts.BackpackLoot.Weights
|
||||
);
|
||||
var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.PocketLoot.Weights);
|
||||
var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.VestLoot.Weights);
|
||||
var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.SpecialItems.Weights);
|
||||
var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(
|
||||
itemCounts.SpecialItems.Weights
|
||||
);
|
||||
var healingItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Healing.Weights);
|
||||
var drugItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Drugs.Weights);
|
||||
var foodItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Food.Weights);
|
||||
@@ -127,7 +140,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Special items
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Special, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.Special,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
specialLootItemCount,
|
||||
botInventory,
|
||||
@@ -138,7 +156,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Healing items / Meds
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.HealingItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.HealingItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
healingItemCount,
|
||||
botInventory,
|
||||
@@ -151,7 +174,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Drugs
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrugItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.DrugItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
drugItemCount,
|
||||
botInventory,
|
||||
@@ -164,7 +192,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Food
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.FoodItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.FoodItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
foodItemCount,
|
||||
botInventory,
|
||||
@@ -177,7 +210,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Drink
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrinkItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.DrinkItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
drinkItemCount,
|
||||
botInventory,
|
||||
@@ -190,7 +228,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Currency
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.CurrencyItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.CurrencyItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
currencyItemCount,
|
||||
botInventory,
|
||||
@@ -203,7 +246,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Stims
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.StimItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.StimItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
containersBotHasAvailable,
|
||||
stimItemCount,
|
||||
botInventory,
|
||||
@@ -216,7 +264,12 @@ public class BotLootGenerator(
|
||||
|
||||
// Grenades
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.GrenadeItems, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.GrenadeItems,
|
||||
botJsonTemplate
|
||||
),
|
||||
[EquipmentSlots.Pockets, EquipmentSlots.TacticalVest], // Can't use containersBotHasEquipped as we don't want grenades added to backpack
|
||||
grenadeCount,
|
||||
botInventory,
|
||||
@@ -270,7 +323,7 @@ public class BotLootGenerator(
|
||||
|
||||
// TacticalVest - generate loot if they have one
|
||||
if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest))
|
||||
// Vest
|
||||
// Vest
|
||||
{
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
@@ -316,7 +369,12 @@ public class BotLootGenerator(
|
||||
if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig))
|
||||
{
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate),
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
isPmc,
|
||||
LootCacheType.Secure,
|
||||
botJsonTemplate
|
||||
),
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
50,
|
||||
botInventory,
|
||||
@@ -337,7 +395,8 @@ public class BotLootGenerator(
|
||||
return null;
|
||||
}
|
||||
|
||||
var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault(minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault(minMaxValue =>
|
||||
botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
);
|
||||
|
||||
return matchingValue;
|
||||
@@ -357,7 +416,8 @@ public class BotLootGenerator(
|
||||
return 0;
|
||||
}
|
||||
|
||||
var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(minMaxValue =>
|
||||
botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
);
|
||||
return matchingValue?.Value;
|
||||
}
|
||||
@@ -367,11 +427,17 @@ public class BotLootGenerator(
|
||||
/// </summary>
|
||||
/// <param name="botInventory">Bot to check</param>
|
||||
/// <returns>Array of available slots</returns>
|
||||
protected HashSet<EquipmentSlots> GetAvailableContainersBotCanStoreItemsIn(BotBaseInventory botInventory)
|
||||
protected HashSet<EquipmentSlots> GetAvailableContainersBotCanStoreItemsIn(
|
||||
BotBaseInventory botInventory
|
||||
)
|
||||
{
|
||||
HashSet<EquipmentSlots> result = [EquipmentSlots.Pockets];
|
||||
|
||||
if ((botInventory.Items ?? []).Any(item => item.SlotId == nameof(EquipmentSlots.TacticalVest)))
|
||||
if (
|
||||
(botInventory.Items ?? []).Any(item =>
|
||||
item.SlotId == nameof(EquipmentSlots.TacticalVest)
|
||||
)
|
||||
)
|
||||
{
|
||||
result.Add(EquipmentSlots.TacticalVest);
|
||||
}
|
||||
@@ -393,10 +459,7 @@ public class BotLootGenerator(
|
||||
{
|
||||
// surv12
|
||||
AddLootFromPool(
|
||||
new Dictionary<string, double>
|
||||
{
|
||||
{ "5d02797c86f774203f38e30a", 1 }
|
||||
},
|
||||
new Dictionary<string, double> { { "5d02797c86f774203f38e30a", 1 } },
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
1,
|
||||
botInventory,
|
||||
@@ -408,10 +471,7 @@ public class BotLootGenerator(
|
||||
|
||||
// AFAK
|
||||
AddLootFromPool(
|
||||
new Dictionary<string, double>
|
||||
{
|
||||
{ "60098ad7c2240c0fe85c570a", 1 }
|
||||
},
|
||||
new Dictionary<string, double> { { "60098ad7c2240c0fe85c570a", 1 } },
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
10,
|
||||
botInventory,
|
||||
@@ -434,8 +494,7 @@ public class BotLootGenerator(
|
||||
/// <param name="containersIdFull"></param>
|
||||
/// <param name="totalValueLimitRub">Total value of loot allowed in roubles</param>
|
||||
/// <param name="isPmc">Is bot being generated for a pmc</param>
|
||||
protected void AddLootFromPool
|
||||
(
|
||||
protected void AddLootFromPool(
|
||||
Dictionary<string, double> pool,
|
||||
HashSet<EquipmentSlots> equipmentSlots,
|
||||
double totalItemCount,
|
||||
@@ -471,12 +530,17 @@ public class BotLootGenerator(
|
||||
|
||||
if (!key)
|
||||
{
|
||||
_logger.Warning($"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}");
|
||||
_logger.Warning(
|
||||
$"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemSpawnLimits is not null && ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits))
|
||||
if (
|
||||
itemSpawnLimits is not null
|
||||
&& ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)
|
||||
)
|
||||
{
|
||||
// Remove item from pool to prevent it being picked again
|
||||
pool.Remove(weightedItemTpl);
|
||||
@@ -492,14 +556,19 @@ public class BotLootGenerator(
|
||||
{
|
||||
Id = newRootItemId,
|
||||
Template = itemToAddTemplate?.Id ?? string.Empty,
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemToAddTemplate, botRole)
|
||||
}
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
|
||||
itemToAddTemplate,
|
||||
botRole
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Is Simple-Wallet / WZ wallet
|
||||
if (_botConfig.WalletLoot.WalletTplPool.Contains(weightedItemTpl))
|
||||
{
|
||||
var addCurrencyToWallet = _randomUtil.GetChance100(_botConfig.WalletLoot.ChancePercent);
|
||||
var addCurrencyToWallet = _randomUtil.GetChance100(
|
||||
_botConfig.WalletLoot.ChancePercent
|
||||
);
|
||||
if (addCurrencyToWallet)
|
||||
{
|
||||
// Create the currency items we want to add to wallet
|
||||
@@ -552,7 +621,9 @@ public class BotLootGenerator(
|
||||
// Bot has no container to put item in, exit
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them");
|
||||
_logger.Debug(
|
||||
$"Unable to add: {totalItemCount} items to bot as it lacks a container to include them"
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -564,9 +635,9 @@ public class BotLootGenerator(
|
||||
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"
|
||||
$"Failed placing item: {itemToAddTemplate.Id} - {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} "
|
||||
+ $"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} "
|
||||
+ $"times, reason: {itemAddedResult}, skipping"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -609,19 +680,20 @@ public class BotLootGenerator(
|
||||
for (var index = 0; index < itemCount; index++)
|
||||
{
|
||||
// Choose the size of the currency stack - default is 5k, 10k, 15k, 20k, 25k
|
||||
var chosenStackCount = _weightedRandomHelper.GetWeightedValue(_botConfig.WalletLoot.StackSizeWeight);
|
||||
var chosenStackCount = _weightedRandomHelper.GetWeightedValue(
|
||||
_botConfig.WalletLoot.StackSizeWeight
|
||||
);
|
||||
List<Item> items =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = _weightedRandomHelper.GetWeightedValue(_botConfig.WalletLoot.CurrencyWeight),
|
||||
Template = _weightedRandomHelper.GetWeightedValue(
|
||||
_botConfig.WalletLoot.CurrencyWeight
|
||||
),
|
||||
ParentId = walletId,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = int.Parse(chosenStackCount)
|
||||
}
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = int.Parse(chosenStackCount) },
|
||||
},
|
||||
];
|
||||
result.Add(items);
|
||||
}
|
||||
@@ -636,7 +708,12 @@ public class BotLootGenerator(
|
||||
/// <param name="itemToAddChildrenTo">Item to add children to</param>
|
||||
/// <param name="isPmc">Is the item being generated for a pmc (affects money/ammo stack sizes)</param>
|
||||
/// <param name="botRole">role bot has that owns item</param>
|
||||
public void AddRequiredChildItemsToParent(TemplateItem? itemToAddTemplate, List<Item> itemToAddChildrenTo, bool isPmc, string botRole)
|
||||
public void AddRequiredChildItemsToParent(
|
||||
TemplateItem? itemToAddTemplate,
|
||||
List<Item> itemToAddChildrenTo,
|
||||
bool isPmc,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
// Fill ammo box
|
||||
if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX))
|
||||
@@ -672,7 +749,8 @@ public class BotLootGenerator(
|
||||
/// <param name="isPmc">are we generating for a pmc</param>
|
||||
/// <param name="botLevel"></param>
|
||||
/// <param name="containersIdFull"></param>
|
||||
public void AddLooseWeaponsToInventorySlot(string sessionId,
|
||||
public void AddLooseWeaponsToInventorySlot(
|
||||
string sessionId,
|
||||
BotBaseInventory botInventory,
|
||||
EquipmentSlots equipmentSlot,
|
||||
BotTypeInventory? templateInventory,
|
||||
@@ -680,14 +758,15 @@ public class BotLootGenerator(
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
int botLevel,
|
||||
HashSet<string>? containersIdFull)
|
||||
HashSet<string>? containersIdFull
|
||||
)
|
||||
{
|
||||
var chosenWeaponType = _randomUtil.GetArrayValue<string>(
|
||||
[
|
||||
EquipmentSlots.FirstPrimaryWeapon.ToString(),
|
||||
EquipmentSlots.FirstPrimaryWeapon.ToString(),
|
||||
EquipmentSlots.FirstPrimaryWeapon.ToString(),
|
||||
EquipmentSlots.Holster.ToString()
|
||||
EquipmentSlots.Holster.ToString(),
|
||||
]
|
||||
);
|
||||
var randomisedWeaponCount = _randomUtil.GetInt(
|
||||
@@ -716,7 +795,9 @@ public class BotLootGenerator(
|
||||
var weaponRootItem = generatedWeapon.Weapon?.FirstOrDefault();
|
||||
if (weaponRootItem is null)
|
||||
{
|
||||
_logger.Error($"Generated loose weapon: {chosenWeaponType} for: {botRole} level: {botLevel} was null, skipping");
|
||||
_logger.Error(
|
||||
$"Generated loose weapon: {chosenWeaponType} for: {botRole} level: {botLevel} was null, skipping"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -733,7 +814,9 @@ public class BotLootGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Failed to add additional weapon: {weaponRootItem.Id} to bot backpack, reason: {result.ToString()}");
|
||||
_logger.Debug(
|
||||
$"Failed to add additional weapon: {weaponRootItem.Id} to bot backpack, reason: {result.ToString()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -746,11 +829,15 @@ public class BotLootGenerator(
|
||||
/// <param name="botRole">Bot type</param>
|
||||
/// <param name="itemSpawnLimits"></param>
|
||||
/// <returns>true if item has reached spawn limit</returns>
|
||||
protected bool ItemHasReachedSpawnLimit(TemplateItem? itemTemplate, string botRole, ItemSpawnLimitSettings? itemSpawnLimits)
|
||||
protected bool ItemHasReachedSpawnLimit(
|
||||
TemplateItem? itemTemplate,
|
||||
string botRole,
|
||||
ItemSpawnLimitSettings? itemSpawnLimits
|
||||
)
|
||||
{
|
||||
// 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
|
||||
// No items found in spawn limit, drop out
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -763,24 +850,24 @@ public class BotLootGenerator(
|
||||
|
||||
var idToCheckFor = GetMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.GlobalLimits);
|
||||
if (idToCheckFor is null)
|
||||
// ParentId or tplid not found in spawnLimits, not a spawn limited item, skip
|
||||
// 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
|
||||
// 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];
|
||||
if (itemSpawnLimits.CurrentLimits[idToCheckFor] > itemSpawnLimits.GlobalLimits[idToCheckFor])
|
||||
if (
|
||||
itemSpawnLimits.CurrentLimits[idToCheckFor] > itemSpawnLimits.GlobalLimits[idToCheckFor]
|
||||
)
|
||||
{
|
||||
// Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely
|
||||
if (currentLimitCount > currentLimitCount * 10)
|
||||
@@ -794,7 +881,7 @@ public class BotLootGenerator(
|
||||
{
|
||||
botRole,
|
||||
itemName = itemTemplate.Name,
|
||||
attempts = currentLimitCount
|
||||
attempts = currentLimitCount,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -827,7 +914,9 @@ public class BotLootGenerator(
|
||||
|
||||
_itemHelper.AddUpdObjectToItem(moneyItem);
|
||||
|
||||
moneyItem.Upd.StackObjectsCount = int.Parse(_weightedRandomHelper.GetWeightedValue(currencyWeight));
|
||||
moneyItem.Upd.StackObjectsCount = int.Parse(
|
||||
_weightedRandomHelper.GetWeightedValue(currencyWeight)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -862,7 +951,12 @@ public class BotLootGenerator(
|
||||
return _botConfig.ItemSpawnLimits[botRole.ToLower()];
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"bot-unable_to_find_spawn_limits_fallback_to_defaults",
|
||||
botRole
|
||||
)
|
||||
);
|
||||
|
||||
return new Dictionary<string, double>();
|
||||
}
|
||||
@@ -873,7 +967,10 @@ public class BotLootGenerator(
|
||||
/// <param name="itemTemplate">item we want to look for in spawn limits</param>
|
||||
/// <param name="spawnLimits">Limits to check for item</param>
|
||||
/// <returns>id as string, otherwise undefined</returns>
|
||||
public string? GetMatchingIdFromSpawnLimits(TemplateItem itemTemplate, Dictionary<string, double> spawnLimits)
|
||||
public string? GetMatchingIdFromSpawnLimits(
|
||||
TemplateItem itemTemplate,
|
||||
Dictionary<string, double> spawnLimits
|
||||
)
|
||||
{
|
||||
if (spawnLimits.ContainsKey(itemTemplate.Id))
|
||||
{
|
||||
|
||||
@@ -36,7 +36,9 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
protected const string _modMagazineSlotId = "mod_magazine";
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(inventoryMagGenComponents);
|
||||
protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(
|
||||
inventoryMagGenComponents
|
||||
);
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
protected RepairConfig _repairConfig = _configServer.GetConfig<RepairConfig>();
|
||||
|
||||
@@ -59,8 +61,16 @@ public class BotWeaponGenerator(
|
||||
/// <param name="isPmc">Is weapon generated for a pmc</param>
|
||||
/// <param name="botLevel"></param>
|
||||
/// <returns>GenerateWeaponResult object</returns>
|
||||
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotTypeInventory botTemplateInventory, string weaponParentId,
|
||||
Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel)
|
||||
public GenerateWeaponResult GenerateRandomWeapon(
|
||||
string sessionId,
|
||||
string equipmentSlot,
|
||||
BotTypeInventory botTemplateInventory,
|
||||
string weaponParentId,
|
||||
Dictionary<string, double> modChances,
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
int botLevel
|
||||
)
|
||||
{
|
||||
var weaponTpl = PickWeightedWeaponTemplateFromPool(equipmentSlot, botTemplateInventory);
|
||||
return GenerateWeaponByTpl(
|
||||
@@ -82,7 +92,10 @@ public class BotWeaponGenerator(
|
||||
/// <param name="equipmentSlot">Primary/secondary/holster</param>
|
||||
/// <param name="botTemplateInventory">e.g. assault.json</param>
|
||||
/// <returns>Weapon template</returns>
|
||||
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotTypeInventory botTemplateInventory)
|
||||
public string PickWeightedWeaponTemplateFromPool(
|
||||
string equipmentSlot,
|
||||
BotTypeInventory botTemplateInventory
|
||||
)
|
||||
{
|
||||
if (!Enum.TryParse(equipmentSlot, out EquipmentSlots key))
|
||||
{
|
||||
@@ -106,8 +119,17 @@ public class BotWeaponGenerator(
|
||||
/// <param name="isPmc">Is weapon being generated for a PMC.</param>
|
||||
/// <param name="botLevel">The level of the bot.</param>
|
||||
/// <returns>GenerateWeaponResult object.</returns>
|
||||
public GenerateWeaponResult? GenerateWeaponByTpl(string sessionId, string weaponTpl, string slotName, BotTypeInventory botTemplateInventory,
|
||||
string weaponParentId, Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel)
|
||||
public GenerateWeaponResult? GenerateWeaponByTpl(
|
||||
string sessionId,
|
||||
string weaponTpl,
|
||||
string slotName,
|
||||
BotTypeInventory botTemplateInventory,
|
||||
string weaponParentId,
|
||||
Dictionary<string, double> modChances,
|
||||
string botRole,
|
||||
bool isPmc,
|
||||
int botLevel
|
||||
)
|
||||
{
|
||||
var modPool = botTemplateInventory.Mods;
|
||||
var weaponItemTemplate = _itemHelper.GetItem(weaponTpl).Value;
|
||||
@@ -140,7 +162,7 @@ public class BotWeaponGenerator(
|
||||
|
||||
// Chance to add randomised weapon enhancement
|
||||
if (isPmc && _randomUtil.GetChance100(_pmcConfig.WeaponHasEnhancementChancePercent))
|
||||
// Add buff to weapon root
|
||||
// Add buff to weapon root
|
||||
{
|
||||
_repairService.AddBuff(_repairConfig.RepairKit.Weapon, weaponWithModsArray[0]);
|
||||
}
|
||||
@@ -166,11 +188,11 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
EquipmentRole = botEquipmentRole,
|
||||
},
|
||||
ModLimits = modLimits,
|
||||
WeaponStats = new WeaponStats(),
|
||||
ConflictingItemTpls = new HashSet<string>()
|
||||
ConflictingItemTpls = new HashSet<string>(),
|
||||
};
|
||||
weaponWithModsArray = _botEquipmentModGenerator.GenerateModsForWeapon(
|
||||
sessionId,
|
||||
@@ -180,7 +202,7 @@ 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
|
||||
// Weapon is bad, fall back to weapons preset
|
||||
{
|
||||
weaponWithModsArray = GetPresetWeaponMods(
|
||||
weaponTpl,
|
||||
@@ -191,7 +213,9 @@ public class BotWeaponGenerator(
|
||||
);
|
||||
}
|
||||
|
||||
var tempList = _cloner.Clone(weaponWithModsArray.Where(item => item.SlotId == _modMagazineSlotId));
|
||||
var tempList = _cloner.Clone(
|
||||
weaponWithModsArray.Where(item => item.SlotId == _modMagazineSlotId)
|
||||
);
|
||||
// Fill existing magazines to full and sync ammo type
|
||||
foreach (var magazine in tempList)
|
||||
{
|
||||
@@ -199,11 +223,18 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
// Add cartridge(s) to gun chamber(s)
|
||||
if (weaponItemTemplate.Properties?.Chambers?.Count > 0 &&
|
||||
weaponItemTemplate.Properties.Chambers.FirstOrDefault().Props.Filters.FirstOrDefault().Filter.Contains(ammoTpl))
|
||||
if (
|
||||
weaponItemTemplate.Properties?.Chambers?.Count > 0
|
||||
&& weaponItemTemplate
|
||||
.Properties.Chambers.FirstOrDefault()
|
||||
.Props.Filters.FirstOrDefault()
|
||||
.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);
|
||||
var chamberSlotNames = weaponItemTemplate.Properties.Chambers.Select(chamberSlot =>
|
||||
chamberSlot.Name
|
||||
);
|
||||
AddCartridgeToChamber(weaponWithModsArray, ammoTpl, chamberSlotNames.ToList());
|
||||
}
|
||||
|
||||
@@ -228,7 +259,7 @@ public class BotWeaponGenerator(
|
||||
ChosenAmmoTemplate = ammoTpl,
|
||||
ChosenUbglAmmoTemplate = ubglAmmoTpl,
|
||||
WeaponMods = modPool,
|
||||
WeaponTemplate = weaponItemTemplate
|
||||
WeaponTemplate = weaponItemTemplate,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,7 +270,11 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponWithModsList">Weapon and mods</param>
|
||||
/// <param name="ammoTemplate">Cartridge to add to weapon</param>
|
||||
/// <param name="chamberSlotIds">Name of slots to create or add ammo to</param>
|
||||
protected void AddCartridgeToChamber(List<Item> weaponWithModsList, string ammoTemplate, List<string> chamberSlotIds)
|
||||
protected void AddCartridgeToChamber(
|
||||
List<Item> weaponWithModsList,
|
||||
string ammoTemplate,
|
||||
List<string> chamberSlotIds
|
||||
)
|
||||
{
|
||||
foreach (var slotId in chamberSlotIds)
|
||||
{
|
||||
@@ -254,10 +289,7 @@ public class BotWeaponGenerator(
|
||||
Template = ammoTemplate,
|
||||
ParentId = weaponWithModsList[0].Id,
|
||||
SlotId = slotId,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = 1 },
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -265,10 +297,7 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
// Already exists, update values
|
||||
existingItemWithSlot.Template = ammoTemplate;
|
||||
existingItemWithSlot.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
existingItemWithSlot.Upd = new Upd { StackObjectsCount = 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,8 +312,13 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponItemTemplate">Database template for weapon</param>
|
||||
/// <param name="botRole">For durability values</param>
|
||||
/// <returns>Base weapon item in a list</returns>
|
||||
protected List<Item> ConstructWeaponBaseList(string weaponTemplate, string weaponParentId, string equipmentSlot, TemplateItem weaponItemTemplate,
|
||||
string botRole)
|
||||
protected List<Item> ConstructWeaponBaseList(
|
||||
string weaponTemplate,
|
||||
string weaponParentId,
|
||||
string equipmentSlot,
|
||||
TemplateItem weaponItemTemplate,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
return
|
||||
[
|
||||
@@ -294,8 +328,11 @@ public class BotWeaponGenerator(
|
||||
Template = weaponTemplate,
|
||||
ParentId = weaponParentId,
|
||||
SlotId = equipmentSlot,
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(weaponItemTemplate, botRole)
|
||||
}
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
|
||||
weaponItemTemplate,
|
||||
botRole
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -308,10 +345,21 @@ public class BotWeaponGenerator(
|
||||
/// <param name="itemTemplate">Item template</param>
|
||||
/// <param name="botRole">Bot role</param>
|
||||
/// <returns>List of weapon mods</returns>
|
||||
protected List<Item> GetPresetWeaponMods(string weaponTemplate, string equipmentSlot, string weaponParentId, TemplateItem itemTemplate, string botRole)
|
||||
protected List<Item> GetPresetWeaponMods(
|
||||
string weaponTemplate,
|
||||
string equipmentSlot,
|
||||
string weaponParentId,
|
||||
TemplateItem itemTemplate,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
// Invalid weapon generated, fallback to preset
|
||||
_logger.Warning(_localisationService.GetText("bot-weapon_generated_incorrect_using_default", $"{weaponTemplate} - {itemTemplate.Name}"));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"bot-weapon_generated_incorrect_using_default",
|
||||
$"{weaponTemplate} - {itemTemplate.Name}"
|
||||
)
|
||||
);
|
||||
List<Item> weaponMods = [];
|
||||
|
||||
// TODO: Preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
|
||||
@@ -331,13 +379,18 @@ public class BotWeaponGenerator(
|
||||
var parentItem = preset.Items[0];
|
||||
parentItem.ParentId = weaponParentId;
|
||||
parentItem.SlotId = equipmentSlot;
|
||||
parentItem.Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate, botRole);
|
||||
parentItem.Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
|
||||
itemTemplate,
|
||||
botRole
|
||||
);
|
||||
preset.Items[0] = parentItem;
|
||||
weaponMods.AddRange(preset.Items);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-missing_weapon_preset", weaponTemplate));
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-missing_weapon_preset", weaponTemplate)
|
||||
);
|
||||
}
|
||||
|
||||
return weaponMods;
|
||||
@@ -360,10 +413,16 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
// 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)) ?? [])
|
||||
foreach (
|
||||
var modSlotTemplate in modTemplate.Properties.Slots?.Where(slot =>
|
||||
slot.Required.GetValueOrDefault(false)
|
||||
) ?? []
|
||||
)
|
||||
{
|
||||
var slotName = modSlotTemplate.Name;
|
||||
var hasWeaponSlotItem = weaponItemList.Any(weaponItem => weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName);
|
||||
var hasWeaponSlotItem = weaponItemList.Any(weaponItem =>
|
||||
weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName
|
||||
);
|
||||
if (!hasWeaponSlotItem)
|
||||
{
|
||||
_logger.Warning(
|
||||
@@ -374,7 +433,7 @@ public class BotWeaponGenerator(
|
||||
modSlot = modSlotTemplate.Name,
|
||||
modName = modTemplate.Name,
|
||||
slotId = mod.SlotId,
|
||||
botRole
|
||||
botRole,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -395,16 +454,27 @@ public class BotWeaponGenerator(
|
||||
/// <param name="magWeights">Magazine weights for count to add to inventory</param>
|
||||
/// <param name="inventory">Inventory to add magazines to</param>
|
||||
/// <param name="botRole">The bot type we're generating extra mags for</param>
|
||||
public void AddExtraMagazinesToInventory(GenerateWeaponResult generatedWeaponResult, GenerationData magWeights, BotBaseInventory inventory, string botRole)
|
||||
public void AddExtraMagazinesToInventory(
|
||||
GenerateWeaponResult generatedWeaponResult,
|
||||
GenerationData magWeights,
|
||||
BotBaseInventory inventory,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
var weaponAndMods = generatedWeaponResult.Weapon;
|
||||
var weaponTemplate = generatedWeaponResult.WeaponTemplate;
|
||||
var magazineTpl = GetMagazineTemplateFromWeaponTemplate(weaponAndMods, weaponTemplate, botRole);
|
||||
var magazineTpl = GetMagazineTemplateFromWeaponTemplate(
|
||||
weaponAndMods,
|
||||
weaponTemplate,
|
||||
botRole
|
||||
);
|
||||
|
||||
var magTemplate = _itemHelper.GetItem(magazineTpl).Value;
|
||||
if (magTemplate is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-unable_to_find_magazine_item", magazineTpl));
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-unable_to_find_magazine_item", magazineTpl)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -414,7 +484,10 @@ public class BotWeaponGenerator(
|
||||
if (!ammoTemplate.Key)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-unable_to_find_ammo_item", generatedWeaponResult.ChosenAmmoTemplate)
|
||||
_localisationService.GetText(
|
||||
"bot-unable_to_find_ammo_item",
|
||||
generatedWeaponResult.ChosenAmmoTemplate
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -434,7 +507,8 @@ public class BotWeaponGenerator(
|
||||
inventory
|
||||
);
|
||||
|
||||
_inventoryMagGenComponents.FirstOrDefault(v => v.CanHandleInventoryMagGen(inventoryMagGenModel))
|
||||
_inventoryMagGenComponents
|
||||
.FirstOrDefault(v => v.CanHandleInventoryMagGen(inventoryMagGenModel))
|
||||
.Process(inventoryMagGenModel);
|
||||
|
||||
// Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly)
|
||||
@@ -452,7 +526,11 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponMods">Weapon list with mods</param>
|
||||
/// <param name="generatedWeaponResult">Result of weapon generation</param>
|
||||
/// <param name="inventory">Bot inventory to add grenades to</param>
|
||||
protected void AddUbglGrenadesToBotInventory(List<Item> weaponMods, GenerateWeaponResult generatedWeaponResult, BotBaseInventory inventory)
|
||||
protected void AddUbglGrenadesToBotInventory(
|
||||
List<Item> weaponMods,
|
||||
GenerateWeaponResult generatedWeaponResult,
|
||||
BotBaseInventory inventory
|
||||
)
|
||||
{
|
||||
// Find ubgl mod item + get details of it from db
|
||||
var ubglMod = weaponMods.FirstOrDefault(x => x.SlotId == "mod_launcher");
|
||||
@@ -461,16 +539,14 @@ public class BotWeaponGenerator(
|
||||
// Define min/max of how many grenades bot will have
|
||||
GenerationData ubglMinMax = new()
|
||||
{
|
||||
Weights = new Dictionary<double, double>
|
||||
{
|
||||
{ 1, 1 },
|
||||
{ 2, 1 }
|
||||
},
|
||||
Whitelist = new Dictionary<string, double>()
|
||||
Weights = new Dictionary<double, double> { { 1, 1 }, { 2, 1 } },
|
||||
Whitelist = new Dictionary<string, double>(),
|
||||
};
|
||||
|
||||
// get ammo template from db
|
||||
var ubglAmmoDbTemplate = _itemHelper.GetItem(generatedWeaponResult.ChosenUbglAmmoTemplate).Value;
|
||||
var ubglAmmoDbTemplate = _itemHelper
|
||||
.GetItem(generatedWeaponResult.ChosenUbglAmmoTemplate)
|
||||
.Value;
|
||||
|
||||
// Add greandes to bot inventory
|
||||
var ubglAmmoGenModel = new InventoryMagGen(
|
||||
@@ -495,16 +571,18 @@ public class BotWeaponGenerator(
|
||||
/// <param name="ammoTpl">Ammo type to add.</param>
|
||||
/// <param name="stackSize">Size of the ammo stack to add.</param>
|
||||
/// <param name="inventory">Player inventory.</param>
|
||||
protected void AddAmmoToSecureContainer(int stackCount, string ammoTpl, int stackSize, BotBaseInventory inventory)
|
||||
protected void AddAmmoToSecureContainer(
|
||||
int stackCount,
|
||||
string ammoTpl,
|
||||
int stackSize,
|
||||
BotBaseInventory inventory
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < stackCount; i++)
|
||||
{
|
||||
var id = _hashUtil.Generate();
|
||||
_botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
|
||||
new HashSet<EquipmentSlots>
|
||||
{
|
||||
EquipmentSlots.SecuredContainer
|
||||
},
|
||||
new HashSet<EquipmentSlots> { EquipmentSlots.SecuredContainer },
|
||||
id,
|
||||
ammoTpl,
|
||||
new List<Item>
|
||||
@@ -513,11 +591,8 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
Id = id,
|
||||
Template = ammoTpl,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackSize
|
||||
}
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = stackSize },
|
||||
},
|
||||
},
|
||||
inventory
|
||||
);
|
||||
@@ -531,7 +606,11 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponTemplate">Weapon to get magazine template for.</param>
|
||||
/// <param name="botRole">The bot type we are getting the magazine for.</param>
|
||||
/// <returns>Magazine template string.</returns>
|
||||
protected string GetMagazineTemplateFromWeaponTemplate(List<Item> weaponMods, TemplateItem weaponTemplate, string botRole)
|
||||
protected string GetMagazineTemplateFromWeaponTemplate(
|
||||
List<Item> weaponMods,
|
||||
TemplateItem weaponTemplate,
|
||||
string botRole
|
||||
)
|
||||
{
|
||||
var magazine = weaponMods.FirstOrDefault(m => m.SlotId == _modMagazineSlotId);
|
||||
if (magazine is null)
|
||||
@@ -545,21 +624,19 @@ public class BotWeaponGenerator(
|
||||
|
||||
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
|
||||
if (!weaponTemplate.Properties.IsChamberLoad ?? false)
|
||||
// Shouldn't happen
|
||||
// Shouldn't happen
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"bot-weapon_missing_magazine_or_chamber",
|
||||
new
|
||||
{
|
||||
weaponId = weaponTemplate.Id,
|
||||
botRole
|
||||
}
|
||||
new { weaponId = weaponTemplate.Id, botRole }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(
|
||||
weaponTemplate
|
||||
);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
@@ -579,10 +656,16 @@ public class BotWeaponGenerator(
|
||||
/// <param name="cartridgePool">Dictionary of all cartridges keyed by type e.g. Caliber556x45NATO</param>
|
||||
/// <param name="weaponTemplate">Weapon details from database we want to pick ammo for</param>
|
||||
/// <returns>Ammo template that works with the desired gun</returns>
|
||||
protected string? GetWeightedCompatibleAmmo(Dictionary<string, Dictionary<string, double>> cartridgePool, TemplateItem weaponTemplate)
|
||||
protected string? GetWeightedCompatibleAmmo(
|
||||
Dictionary<string, Dictionary<string, double>> cartridgePool,
|
||||
TemplateItem weaponTemplate
|
||||
)
|
||||
{
|
||||
var desiredCaliber = GetWeaponCaliber(weaponTemplate);
|
||||
if (!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon) || cartridgePoolForWeapon?.Count == 0)
|
||||
if (
|
||||
!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon)
|
||||
|| cartridgePoolForWeapon?.Count == 0
|
||||
)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
@@ -593,7 +676,7 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
weaponId = weaponTemplate.Id,
|
||||
weaponName = weaponTemplate.Name,
|
||||
defaultAmmo = weaponTemplate.Properties.DefAmmo
|
||||
defaultAmmo = weaponTemplate.Properties.DefAmmo,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -605,9 +688,11 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
// Get cartridges the weapons first chamber allow
|
||||
var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(weaponTemplate);
|
||||
var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(
|
||||
weaponTemplate
|
||||
);
|
||||
if (compatibleCartridgesInTemplate.Count == 0)
|
||||
// No chamber data found in weapon, send default
|
||||
// No chamber data found in weapon, send default
|
||||
{
|
||||
return weaponTemplate.Properties.DefAmmo;
|
||||
}
|
||||
@@ -626,7 +711,9 @@ public class BotWeaponGenerator(
|
||||
if (!compatibleCartridges.Any())
|
||||
{
|
||||
// Get cartridges from the weapons first magazine in filters
|
||||
var compatibleCartridgesInMagazine = GetCompatibleCartridgesFromMagazineTemplate(weaponTemplate);
|
||||
var compatibleCartridgesInMagazine = GetCompatibleCartridgesFromMagazineTemplate(
|
||||
weaponTemplate
|
||||
);
|
||||
if (compatibleCartridgesInMagazine.Count == 0)
|
||||
{
|
||||
// No compatible cartridges found in magazine, use default
|
||||
@@ -634,7 +721,9 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
// Get the caliber data from the first compatible round in the magazine
|
||||
var magazineCaliberData = _itemHelper.GetItem(compatibleCartridgesInMagazine.FirstOrDefault()).Value.Properties.Caliber;
|
||||
var magazineCaliberData = _itemHelper
|
||||
.GetItem(compatibleCartridgesInMagazine.FirstOrDefault())
|
||||
.Value.Properties.Caliber;
|
||||
cartridgePoolForWeapon = cartridgePool[magazineCaliberData];
|
||||
|
||||
foreach (var cartridgeKvP in cartridgePoolForWeapon)
|
||||
@@ -664,7 +753,9 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(weaponTemplate);
|
||||
|
||||
var cartridges = weaponTemplate.Properties?.Chambers?.FirstOrDefault()?.Props?.Filters?[0].Filter;
|
||||
var cartridges = weaponTemplate
|
||||
.Properties?.Chambers?.FirstOrDefault()
|
||||
?.Props?.Filters?[0].Filter;
|
||||
if (cartridges is not null)
|
||||
{
|
||||
return cartridges;
|
||||
@@ -680,26 +771,39 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponTemplate">Weapon db template to get magazine cartridges for</param>
|
||||
/// <returns>Hashset of cartridge tpls</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when weaponTemplate is null.</exception>
|
||||
protected HashSet<string> GetCompatibleCartridgesFromMagazineTemplate(TemplateItem weaponTemplate)
|
||||
protected HashSet<string> GetCompatibleCartridgesFromMagazineTemplate(
|
||||
TemplateItem weaponTemplate
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(weaponTemplate);
|
||||
|
||||
// Get the first magazine's template from the weapon
|
||||
var magazineSlot = weaponTemplate.Properties.Slots?.FirstOrDefault(slot => slot.Name == "mod_magazine");
|
||||
var magazineSlot = weaponTemplate.Properties.Slots?.FirstOrDefault(slot =>
|
||||
slot.Name == "mod_magazine"
|
||||
);
|
||||
if (magazineSlot is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var magazineTemplate = _itemHelper.GetItem(magazineSlot.Props?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault());
|
||||
var magazineTemplate = _itemHelper.GetItem(
|
||||
magazineSlot.Props?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault()
|
||||
);
|
||||
if (!magazineTemplate.Key)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// Try to get cartridges from slots array first, if none found, try Cartridges array
|
||||
var cartridges = magazineTemplate.Value.Properties.Slots.FirstOrDefault()?.Props?.Filters.FirstOrDefault()?.Filter
|
||||
?? magazineTemplate.Value.Properties.Cartridges.FirstOrDefault()?.Props?.Filters.FirstOrDefault()?.Filter;
|
||||
var cartridges =
|
||||
magazineTemplate
|
||||
.Value.Properties.Slots.FirstOrDefault()
|
||||
?.Props?.Filters.FirstOrDefault()
|
||||
?.Filter
|
||||
?? magazineTemplate
|
||||
.Value.Properties.Cartridges.FirstOrDefault()
|
||||
?.Props?.Filters.FirstOrDefault()
|
||||
?.Filter;
|
||||
|
||||
return cartridges ?? [];
|
||||
}
|
||||
@@ -717,7 +821,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(weaponTemplate.Properties.AmmoCaliber))
|
||||
// 9x18pmm has a typo, should be Caliber9x18PM
|
||||
// 9x18pmm has a typo, should be Caliber9x18PM
|
||||
{
|
||||
return weaponTemplate.Properties.AmmoCaliber == "Caliber9x18PMM"
|
||||
? "Caliber9x18PM"
|
||||
@@ -729,9 +833,7 @@ public class BotWeaponGenerator(
|
||||
var ammoInChamber = _itemHelper.GetItem(
|
||||
weaponTemplate.Properties.Chambers[0].Props.Filters[0].Filter.FirstOrDefault()
|
||||
);
|
||||
return !ammoInChamber.Key
|
||||
? null
|
||||
: ammoInChamber.Value.Properties.Caliber;
|
||||
return !ammoInChamber.Key ? null : ammoInChamber.Value.Properties.Caliber;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -743,12 +845,18 @@ public class BotWeaponGenerator(
|
||||
/// <param name="weaponMods">Weapon with children</param>
|
||||
/// <param name="magazine">Magazine item</param>
|
||||
/// <param name="cartridgeTemplate">Cartridge to insert into magazine</param>
|
||||
protected void FillExistingMagazines(List<Item> weaponMods, Item magazine, string cartridgeTemplate)
|
||||
protected void FillExistingMagazines(
|
||||
List<Item> weaponMods,
|
||||
Item magazine,
|
||||
string cartridgeTemplate
|
||||
)
|
||||
{
|
||||
var magazineTemplate = _itemHelper.GetItem(magazine.Template).Value;
|
||||
if (magazineTemplate is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-unable_to_find_magazine_item", magazine.Template));
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-unable_to_find_magazine_item", magazine.Template)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -765,7 +873,12 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTemplate, magazineTemplate);
|
||||
AddOrUpdateMagazinesChildWithAmmo(
|
||||
weaponMods,
|
||||
magazine,
|
||||
cartridgeTemplate,
|
||||
magazineTemplate
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,10 +897,7 @@ public class BotWeaponGenerator(
|
||||
Template = ubglAmmoTpl,
|
||||
ParentId = ubglMod.Id,
|
||||
SlotId = "patron_in_weapon",
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = 1 },
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -799,9 +909,15 @@ public class BotWeaponGenerator(
|
||||
/// <param name="magazine">Magazine item details we're adding cartridges to</param>
|
||||
/// <param name="chosenAmmoTpl">Cartridge to put into the magazine</param>
|
||||
/// <param name="magazineTemplate">Magazines db template</param>
|
||||
protected void AddOrUpdateMagazinesChildWithAmmo(List<Item> weaponWithMods, Item magazine, string chosenAmmoTpl, TemplateItem magazineTemplate)
|
||||
protected void AddOrUpdateMagazinesChildWithAmmo(
|
||||
List<Item> weaponWithMods,
|
||||
Item magazine,
|
||||
string chosenAmmoTpl,
|
||||
TemplateItem magazineTemplate
|
||||
)
|
||||
{
|
||||
var magazineCartridgeChildItem = weaponWithMods.FirstOrDefault(m => m.ParentId == magazine.Id && m.SlotId == "cartridges"
|
||||
var magazineCartridgeChildItem = weaponWithMods.FirstOrDefault(m =>
|
||||
m.ParentId == magazine.Id && m.SlotId == "cartridges"
|
||||
);
|
||||
if (magazineCartridgeChildItem is not null)
|
||||
{
|
||||
@@ -813,13 +929,20 @@ public class BotWeaponGenerator(
|
||||
List<Item> magazineWithCartridges = [magazine];
|
||||
|
||||
// Add cartridges as children to above mag array
|
||||
_itemHelper.FillMagazineWithCartridge(magazineWithCartridges, magazineTemplate, chosenAmmoTpl, 1);
|
||||
_itemHelper.FillMagazineWithCartridge(
|
||||
magazineWithCartridges,
|
||||
magazineTemplate,
|
||||
chosenAmmoTpl,
|
||||
1
|
||||
);
|
||||
|
||||
// Replace existing magazine with above array of mag + cartridge stacks
|
||||
var magazineIndex = weaponWithMods.FindIndex(i => i.Id == magazine.Id); // magazineWithCartridges
|
||||
if (magazineIndex == -1)
|
||||
{
|
||||
_logger.Error($"Unable to add cartridges: {chosenAmmoTpl} to magazine: {magazine.Id} as none found");
|
||||
_logger.Error(
|
||||
$"Unable to add cartridges: {chosenAmmoTpl} to magazine: {magazine.Id} as none found"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -842,7 +965,9 @@ public class BotWeaponGenerator(
|
||||
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type,
|
||||
// but I'm not sure whether this is also used elsewhere
|
||||
var camoras = weaponMods
|
||||
.Where(x => x.ParentId == magazineId && x.SlotId.StartsWith("camora", StringComparison.Ordinal))
|
||||
.Where(x =>
|
||||
x.ParentId == magazineId && x.SlotId.StartsWith("camora", StringComparison.Ordinal)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
if (camoras.Count == 0)
|
||||
@@ -859,10 +984,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
else
|
||||
{
|
||||
camora.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
camora.Upd = new Upd { StackObjectsCount = 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,24 +60,29 @@ public class FenceBaseAssortGenerator(
|
||||
// Item base type blacklisted
|
||||
if (traderConfig.Fence.Blacklist.Count > 0)
|
||||
{
|
||||
if (traderConfig.Fence.Blacklist.Contains(rootItemDb.Id) ||
|
||||
itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist)
|
||||
)
|
||||
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
|
||||
)
|
||||
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))
|
||||
if (
|
||||
traderConfig.Fence.BlacklistSeasonalItems
|
||||
&& blockedSeasonalItems.Contains(rootItemDb.Id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -91,11 +96,8 @@ 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
|
||||
@@ -108,7 +110,7 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
|
||||
// Only add cartridges to box if box has no children
|
||||
// Only add cartridges to box if box has no children
|
||||
{
|
||||
if (itemWithChildrenToAdd.Count == 1)
|
||||
{
|
||||
@@ -127,12 +129,17 @@ public class FenceBaseAssortGenerator(
|
||||
// Create barter scheme (price)
|
||||
var barterSchemeToAdd = new BarterScheme
|
||||
{
|
||||
Count = Math.Round((double) fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)),
|
||||
Template = Money.ROUBLES
|
||||
Count = Math.Round(
|
||||
(double)fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)
|
||||
),
|
||||
Template = Money.ROUBLES,
|
||||
};
|
||||
|
||||
// Add barter data to base
|
||||
baseFenceAssort.BarterScheme[itemWithChildrenToAdd[0].Id] = [[barterSchemeToAdd]];
|
||||
baseFenceAssort.BarterScheme[itemWithChildrenToAdd[0].Id] =
|
||||
[
|
||||
[barterSchemeToAdd],
|
||||
];
|
||||
|
||||
// Add item to base
|
||||
baseFenceAssort.Items.AddRange(itemWithChildrenToAdd);
|
||||
@@ -146,7 +153,11 @@ 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))
|
||||
if (
|
||||
baseFenceAssort.Items.Any(item =>
|
||||
item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id
|
||||
)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -167,8 +178,7 @@ public class FenceBaseAssortGenerator(
|
||||
mod.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1,
|
||||
SptPresetId =
|
||||
defaultPreset.Id // Store preset id here so we can check it later to prevent preset dupes
|
||||
SptPresetId = defaultPreset.Id, // Store preset id here so we can check it later to prevent preset dupes
|
||||
};
|
||||
|
||||
// Updated root item, exit loop
|
||||
@@ -191,9 +201,9 @@ public class FenceBaseAssortGenerator(
|
||||
new BarterScheme
|
||||
{
|
||||
Template = Money.ROUBLES,
|
||||
Count = Math.Round(price * itemQualityModifier)
|
||||
}
|
||||
}
|
||||
Count = Math.Round(price * itemQualityModifier),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
baseFenceAssort.LoyalLevelItems[itemAndChildren[0].Id] = 1;
|
||||
@@ -210,7 +220,12 @@ public class FenceBaseAssortGenerator(
|
||||
var ammoPenetrationPower = GetAmmoPenetrationPower(rootItemDb);
|
||||
if (ammoPenetrationPower == null)
|
||||
{
|
||||
logger.Warning(localisationService.GetText("fence-unable_to_get_ammo_penetration_value", rootItemDb.Id));
|
||||
logger.Warning(
|
||||
localisationService.GetText(
|
||||
"fence-unable_to_get_ammo_penetration_value",
|
||||
rootItemDb.Id
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -227,13 +242,18 @@ public class FenceBaseAssortGenerator(
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
// Get the cartridge tpl found inside ammo box
|
||||
var cartridgeTplInBox = rootItemDb.Properties.StackSlots[0].Props.Filters[0].Filter.FirstOrDefault();
|
||||
var cartridgeTplInBox = rootItemDb
|
||||
.Properties.StackSlots[0]
|
||||
.Props.Filters[0]
|
||||
.Filter.FirstOrDefault();
|
||||
|
||||
// Look up cartridge tpl in db
|
||||
var ammoItemDb = itemHelper.GetItem(cartridgeTplInBox);
|
||||
if (!ammoItemDb.Key)
|
||||
{
|
||||
logger.Warning(localisationService.GetText("fence-ammo_not_found_in_db", cartridgeTplInBox));
|
||||
logger.Warning(
|
||||
localisationService.GetText("fence-ammo_not_found_in_db", cartridgeTplInBox)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -265,17 +285,20 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
|
||||
// Check for and add required soft inserts to armors
|
||||
var requiredSlots = itemDbDetails.Properties.Slots.Where(slot => slot.Required ?? false).ToList();
|
||||
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
|
||||
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
|
||||
// Some bsg plate properties are empty, skip mod
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -291,9 +314,9 @@ public class FenceBaseAssortGenerator(
|
||||
Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = modItemDbDetails.Properties.MaxDurability,
|
||||
MaxDurability = modItemDbDetails.Properties.MaxDurability
|
||||
}
|
||||
}
|
||||
MaxDurability = modItemDbDetails.Properties.MaxDurability,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
armor.Add(mod);
|
||||
@@ -301,7 +324,8 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
|
||||
// Check for and add plate items
|
||||
var plateSlots = itemDbDetails.Properties.Slots.Where(slot => itemHelper.IsRemovablePlateSlot(slot.Name))
|
||||
var plateSlots = itemDbDetails
|
||||
.Properties.Slots.Where(slot => itemHelper.IsRemovablePlateSlot(slot.Name))
|
||||
.ToList();
|
||||
if (plateSlots.Count > 0)
|
||||
{
|
||||
@@ -309,7 +333,7 @@ public class FenceBaseAssortGenerator(
|
||||
{
|
||||
var plateTpl = plateSlot.Props.Filters[0].Plate;
|
||||
if (string.IsNullOrEmpty(plateTpl))
|
||||
// Bsg data lacks a default plate, skip adding mod
|
||||
// Bsg data lacks a default plate, skip adding mod
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -327,9 +351,9 @@ public class FenceBaseAssortGenerator(
|
||||
Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = modItemDbDetails.Properties.MaxDurability,
|
||||
MaxDurability = modItemDbDetails.Properties.MaxDurability
|
||||
}
|
||||
}
|
||||
MaxDurability = modItemDbDetails.Properties.MaxDurability,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,14 +33,17 @@ public class LocationLootGenerator(
|
||||
)
|
||||
{
|
||||
protected readonly LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>();
|
||||
protected readonly SeasonalEventConfig _seasonalEventConfig = _configServer.GetConfig<SeasonalEventConfig>();
|
||||
protected readonly SeasonalEventConfig _seasonalEventConfig =
|
||||
_configServer.GetConfig<SeasonalEventConfig>();
|
||||
|
||||
/// Create a list of container objects with randomised loot
|
||||
/// <param name="locationBase">Map base to generate containers for</param>
|
||||
/// <param name="staticAmmoDist">Static ammo distribution</param>
|
||||
/// <returns>List of container objects</returns>
|
||||
public List<SpawnpointTemplate> GenerateStaticContainers(LocationBase locationBase,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist)
|
||||
public List<SpawnpointTemplate> GenerateStaticContainers(
|
||||
LocationBase locationBase,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist
|
||||
)
|
||||
{
|
||||
var staticLootItemCount = 0;
|
||||
var result = new List<SpawnpointTemplate>();
|
||||
@@ -52,18 +55,26 @@ public class LocationLootGenerator(
|
||||
if (staticWeaponsOnMapClone is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("location-unable_to_find_static_weapon_for_map", locationBase.Name)
|
||||
_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);
|
||||
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)
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_find_static_container_for_map",
|
||||
locationBase.Name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,12 +93,16 @@ public class LocationLootGenerator(
|
||||
// Remove christmas items from loot data
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
{
|
||||
allStaticContainersOnMapClone = allStaticContainersOnMapClone.Where(item => !_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id)
|
||||
allStaticContainersOnMapClone = allStaticContainersOnMapClone
|
||||
.Where(item =>
|
||||
!_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(allStaticContainersOnMapClone);
|
||||
var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(
|
||||
allStaticContainersOnMapClone
|
||||
);
|
||||
|
||||
// Keep track of static loot count
|
||||
var staticContainerCount = 0;
|
||||
@@ -98,13 +113,17 @@ public class LocationLootGenerator(
|
||||
staticContainerCount += guaranteedContainers.Count;
|
||||
|
||||
// Add loot to guaranteed containers and add to result
|
||||
foreach (var containerWithLoot in guaranteedContainers.Select(container => AddLootToContainer(
|
||||
container,
|
||||
staticForcedOnMapClone,
|
||||
staticLootDist.Value,
|
||||
staticAmmoDist,
|
||||
locationId
|
||||
)))
|
||||
foreach (
|
||||
var containerWithLoot in guaranteedContainers.Select(container =>
|
||||
AddLootToContainer(
|
||||
container,
|
||||
staticForcedOnMapClone,
|
||||
staticLootDist.Value,
|
||||
staticAmmoDist,
|
||||
locationId
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
result.Add(containerWithLoot.Template);
|
||||
|
||||
@@ -117,8 +136,10 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// Randomisation is turned off globally or just turned off for this map
|
||||
if (!_locationConfig.ContainerRandomisationSettings.Enabled || !_locationConfig.ContainerRandomisationSettings.Maps.ContainsKey(locationId)
|
||||
)
|
||||
if (
|
||||
!_locationConfig.ContainerRandomisationSettings.Enabled
|
||||
|| !_locationConfig.ContainerRandomisationSettings.Maps.ContainsKey(locationId)
|
||||
)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
@@ -149,15 +170,18 @@ public class LocationLootGenerator(
|
||||
// Group containers by their groupId
|
||||
if (mapData.Statics is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("location-unable_to_generate_static_loot", locationId));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-unable_to_generate_static_loot", locationId)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
|
||||
var mapping = GetGroupIdToContainerMappings(mapData.Statics, staticRandomisableContainersOnMap);
|
||||
var mapping = GetGroupIdToContainerMappings(
|
||||
mapData.Statics,
|
||||
staticRandomisableContainersOnMap
|
||||
);
|
||||
foreach (var (key, data) in mapping)
|
||||
{
|
||||
// Count chosen was 0, skip
|
||||
@@ -170,7 +194,9 @@ public class LocationLootGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping");
|
||||
_logger.Debug(
|
||||
$"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -186,7 +212,9 @@ public class LocationLootGenerator(
|
||||
{
|
||||
if (_randomUtil.GetChance100(containerIdsCopy[containerId.Key] * 100))
|
||||
{
|
||||
data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[containerId.Key];
|
||||
data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[
|
||||
containerId.Key
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +233,8 @@ public class LocationLootGenerator(
|
||||
foreach (var chosenContainerId in chosenContainerIds)
|
||||
{
|
||||
// Look up container object from full list of containers on map
|
||||
var containerObject = staticRandomisableContainersOnMap.FirstOrDefault(staticContainer => staticContainer.Template.Id == chosenContainerId
|
||||
var containerObject = staticRandomisableContainersOnMap.FirstOrDefault(
|
||||
staticContainer => staticContainer.Template.Id == chosenContainerId
|
||||
);
|
||||
if (containerObject is null)
|
||||
{
|
||||
@@ -236,7 +265,10 @@ public class LocationLootGenerator(
|
||||
|
||||
_logger.Success($"A total of: {staticLootItemCount} static items spawned");
|
||||
_logger.Success(
|
||||
_localisationService.GetText("location-containers_generated_success", staticContainerCount)
|
||||
_localisationService.GetText(
|
||||
"location-containers_generated_success",
|
||||
staticContainerCount
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
@@ -247,12 +279,15 @@ public class LocationLootGenerator(
|
||||
/// </summary>
|
||||
/// <param name="staticContainers"></param>
|
||||
/// <returns>StaticContainerData array</returns>
|
||||
protected List<StaticContainerData> GetRandomisableContainersOnMap(List<StaticContainerData> staticContainers)
|
||||
protected List<StaticContainerData> GetRandomisableContainersOnMap(
|
||||
List<StaticContainerData> staticContainers
|
||||
)
|
||||
{
|
||||
return staticContainers.Where(staticContainer =>
|
||||
staticContainer.Probability != 1 &&
|
||||
!staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) &&
|
||||
!_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
return staticContainers
|
||||
.Where(staticContainer =>
|
||||
staticContainer.Probability != 1
|
||||
&& !staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false)
|
||||
&& !_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
staticContainer.Template.Items.FirstOrDefault().Template
|
||||
)
|
||||
)
|
||||
@@ -264,12 +299,15 @@ public class LocationLootGenerator(
|
||||
/// </summary>
|
||||
/// <param name="staticContainersOnMap"></param>
|
||||
/// <returns>IStaticContainerData array</returns>
|
||||
protected List<StaticContainerData> GetGuaranteedContainers(List<StaticContainerData> staticContainersOnMap)
|
||||
protected List<StaticContainerData> GetGuaranteedContainers(
|
||||
List<StaticContainerData> staticContainersOnMap
|
||||
)
|
||||
{
|
||||
return staticContainersOnMap.Where(staticContainer =>
|
||||
staticContainer.Probability == 1 ||
|
||||
staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) ||
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
return staticContainersOnMap
|
||||
.Where(staticContainer =>
|
||||
staticContainer.Probability == 1
|
||||
|| staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false)
|
||||
|| _locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
staticContainer.Template.Items.FirstOrDefault().Template
|
||||
)
|
||||
)
|
||||
@@ -283,7 +321,10 @@ public class LocationLootGenerator(
|
||||
/// <param name="groupId">Name of the group the containers are being collected for</param>
|
||||
/// <param name="containerData">Containers and probability values for a groupId</param>
|
||||
/// <returns>List of chosen container Ids</returns>
|
||||
protected List<string> GetContainersByProbability(string groupId, ContainerGroupCount containerData)
|
||||
protected List<string> GetContainersByProbability(
|
||||
string groupId,
|
||||
ContainerGroupCount containerData
|
||||
)
|
||||
{
|
||||
var chosenContainerIds = new List<string>();
|
||||
|
||||
@@ -301,15 +342,14 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// Create probability array with all possible container ids in this group and their relative probability of spawning
|
||||
var containerDistribution =
|
||||
new ProbabilityObjectArray<string, double>(_mathUtil, _cloner);
|
||||
var containerDistribution = new ProbabilityObjectArray<string, double>(_mathUtil, _cloner);
|
||||
foreach (var x in containerIds)
|
||||
{
|
||||
var value = containerData.ContainerIdsWithProbability[x];
|
||||
containerDistribution.Add(new ProbabilityObject<string, double>(x, value, value));
|
||||
}
|
||||
|
||||
chosenContainerIds.AddRange(containerDistribution.Draw((int) containerData.ChosenCount));
|
||||
chosenContainerIds.AddRange(containerDistribution.Draw((int)containerData.ChosenCount));
|
||||
|
||||
return chosenContainerIds;
|
||||
}
|
||||
@@ -322,7 +362,8 @@ public class LocationLootGenerator(
|
||||
/// <returns>dictionary keyed by groupId</returns>
|
||||
protected Dictionary<string, ContainerGroupCount> GetGroupIdToContainerMappings(
|
||||
StaticContainer staticContainerGroupData,
|
||||
List<StaticContainerData> staticContainersOnMap)
|
||||
List<StaticContainerData> staticContainersOnMap
|
||||
)
|
||||
{
|
||||
// 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>();
|
||||
@@ -332,31 +373,45 @@ public class LocationLootGenerator(
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = _randomUtil.GetInt(
|
||||
(int) Math.Round(
|
||||
groupKvP.Value.MinContainers.Value *
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerGroupMinSizeMultiplier
|
||||
),
|
||||
(int) Math.Round(
|
||||
groupKvP.Value.MaxContainers.Value *
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerGroupMaxSizeMultiplier
|
||||
)
|
||||
)
|
||||
(int)
|
||||
Math.Round(
|
||||
groupKvP.Value.MinContainers.Value
|
||||
* _locationConfig
|
||||
.ContainerRandomisationSettings
|
||||
.ContainerGroupMinSizeMultiplier
|
||||
),
|
||||
(int)
|
||||
Math.Round(
|
||||
groupKvP.Value.MaxContainers.Value
|
||||
* _locationConfig
|
||||
.ContainerRandomisationSettings
|
||||
.ContainerGroupMaxSizeMultiplier
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// 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`
|
||||
mapping.Add(string.Empty, new ContainerGroupCount
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = -1
|
||||
});
|
||||
mapping.Add(
|
||||
string.Empty,
|
||||
new ContainerGroupCount
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = -1,
|
||||
}
|
||||
);
|
||||
|
||||
// Iterate over all containers and add to group keyed by groupId
|
||||
// Containers without a group go into a group with empty key ""
|
||||
foreach (var container in staticContainersOnMap)
|
||||
{
|
||||
if (!staticContainerGroupData.Containers.TryGetValue(container.Template.Id, out var groupData))
|
||||
if (
|
||||
!staticContainerGroupData.Containers.TryGetValue(
|
||||
container.Template.Id,
|
||||
out var groupData
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
@@ -385,10 +440,14 @@ public class LocationLootGenerator(
|
||||
new ContainerGroupCount
|
||||
{
|
||||
ChosenCount = 0d,
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>()
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
}
|
||||
);
|
||||
mapping[groupData.GroupId].ContainerIdsWithProbability.TryAdd(container.Template.Id, container.Probability.Value);
|
||||
mapping[groupData.GroupId]
|
||||
.ContainerIdsWithProbability.TryAdd(
|
||||
container.Template.Id,
|
||||
container.Probability.Value
|
||||
);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
@@ -404,7 +463,8 @@ public class LocationLootGenerator(
|
||||
/// <param name="staticAmmoDist">staticAmmo.json</param>
|
||||
/// <param name="locationName">Name of the map to generate static loot for</param>
|
||||
/// <returns>StaticContainerData</returns>
|
||||
protected StaticContainerData AddLootToContainer(StaticContainerData staticContainer,
|
||||
protected StaticContainerData AddLootToContainer(
|
||||
StaticContainerData staticContainer,
|
||||
List<StaticForced>? staticForced,
|
||||
Dictionary<string, StaticLootDetails> staticLootDist,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
|
||||
@@ -422,7 +482,11 @@ public class LocationLootGenerator(
|
||||
var containerMap = _itemHelper.GetContainerMapping(containerTpl);
|
||||
|
||||
// Choose count of items to add to container
|
||||
var itemCountToAdd = GetWeightedCountOfContainerItems(containerTpl, staticLootDist, locationName);
|
||||
var itemCountToAdd = GetWeightedCountOfContainerItems(
|
||||
containerTpl,
|
||||
staticLootDist,
|
||||
locationName
|
||||
);
|
||||
if (itemCountToAdd == 0)
|
||||
{
|
||||
return containerClone;
|
||||
@@ -468,11 +532,15 @@ public class LocationLootGenerator(
|
||||
: chosenItemWithChildren.Items;
|
||||
|
||||
// look for open slot to put chosen item into
|
||||
var result = _containerHelper.FindSlotForItem(containerMap, chosenItemWithChildren.Width, chosenItemWithChildren.Height);
|
||||
var result = _containerHelper.FindSlotForItem(
|
||||
containerMap,
|
||||
chosenItemWithChildren.Width,
|
||||
chosenItemWithChildren.Height
|
||||
);
|
||||
if (!result.Success.GetValueOrDefault(false))
|
||||
{
|
||||
if (failedToFitAttemptCount > _locationConfig.FitLootIntoContainerAttempts)
|
||||
// x attempts to fit an item, container is probably full, stop trying to add more
|
||||
// x attempts to fit an item, container is probably full, stop trying to add more
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -499,7 +567,9 @@ public class LocationLootGenerator(
|
||||
{
|
||||
X = result.X,
|
||||
Y = result.Y,
|
||||
R = result.Rotation.GetValueOrDefault(false) ? ItemRotation.Vertical : ItemRotation.Horizontal
|
||||
R = result.Rotation.GetValueOrDefault(false)
|
||||
? ItemRotation.Vertical
|
||||
: ItemRotation.Horizontal,
|
||||
};
|
||||
|
||||
// Add loot to container before returning
|
||||
@@ -516,23 +586,21 @@ public class LocationLootGenerator(
|
||||
/// <param name="staticLootDist">staticLoot.json</param>
|
||||
/// <param name="locationName">Map name (to get per-map multiplier for from config)</param>
|
||||
/// <returns>item count</returns>
|
||||
protected int GetWeightedCountOfContainerItems(string containerTypeId,
|
||||
Dictionary<string, StaticLootDetails> staticLootDist, string locationName)
|
||||
protected int GetWeightedCountOfContainerItems(
|
||||
string containerTypeId,
|
||||
Dictionary<string, StaticLootDetails> staticLootDist,
|
||||
string locationName
|
||||
)
|
||||
{
|
||||
// Create probability array to calculate the total count of lootable items inside container
|
||||
var itemCountArray =
|
||||
new ProbabilityObjectArray<int, float?>(_mathUtil, _cloner);
|
||||
var itemCountArray = new ProbabilityObjectArray<int, float?>(_mathUtil, _cloner);
|
||||
var countDistribution = staticLootDist[containerTypeId]?.ItemCountDistribution;
|
||||
if (countDistribution is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_find_count_distribution_for_container",
|
||||
new
|
||||
{
|
||||
containerId = containerTypeId,
|
||||
locationName
|
||||
}
|
||||
new { containerId = containerTypeId, locationName }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -551,7 +619,8 @@ public class LocationLootGenerator(
|
||||
);
|
||||
}
|
||||
|
||||
return (int) Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]);
|
||||
return (int)
|
||||
Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -563,18 +632,23 @@ public class LocationLootGenerator(
|
||||
/// <returns>ProbabilityObjectArray of item tpls + probability</returns>
|
||||
protected ProbabilityObjectArray<string, float?> GetPossibleLootItemsForContainer(
|
||||
string containerTypeId,
|
||||
Dictionary<string, StaticLootDetails> staticLootDist)
|
||||
Dictionary<string, StaticLootDetails> staticLootDist
|
||||
)
|
||||
{
|
||||
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
|
||||
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
|
||||
var itemDistribution =
|
||||
new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
|
||||
var itemDistribution = new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
|
||||
|
||||
var itemContainerDistribution = staticLootDist[containerTypeId]?.ItemDistribution;
|
||||
if (itemContainerDistribution is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("location-missing_item_distribution_data", containerTypeId));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-missing_item_distribution_data",
|
||||
containerTypeId
|
||||
)
|
||||
);
|
||||
|
||||
return itemDistribution;
|
||||
}
|
||||
@@ -586,14 +660,16 @@ public class LocationLootGenerator(
|
||||
// Skip seasonal event items if they're not enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl))
|
||||
{
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
continue;
|
||||
}
|
||||
|
||||
itemDistribution.Add(new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null));
|
||||
itemDistribution.Add(
|
||||
new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null)
|
||||
);
|
||||
}
|
||||
|
||||
return itemDistribution;
|
||||
@@ -620,9 +696,11 @@ public class LocationLootGenerator(
|
||||
/// <param name="staticAmmoDist"></param>
|
||||
/// <param name="locationName">Location to generate loot for</param>
|
||||
/// <returns>Array of spawn points with loot in them</returns>
|
||||
public List<SpawnpointTemplate> GenerateDynamicLoot(LooseLoot dynamicLootDist,
|
||||
public List<SpawnpointTemplate> GenerateDynamicLoot(
|
||||
LooseLoot dynamicLootDist,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
|
||||
string locationName)
|
||||
string locationName
|
||||
)
|
||||
{
|
||||
List<SpawnpointTemplate> loot = [];
|
||||
List<Spawnpoint> dynamicForcedSpawnPoints = [];
|
||||
@@ -630,11 +708,13 @@ public class LocationLootGenerator(
|
||||
// Remove christmas items from loot data
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
{
|
||||
dynamicLootDist.Spawnpoints = dynamicLootDist.Spawnpoints.Where(point =>
|
||||
dynamicLootDist.Spawnpoints = dynamicLootDist
|
||||
.Spawnpoints.Where(point =>
|
||||
!point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
.ToList();
|
||||
dynamicLootDist.SpawnpointsForced = dynamicLootDist.SpawnpointsForced.Where(point =>
|
||||
dynamicLootDist.SpawnpointsForced = dynamicLootDist
|
||||
.SpawnpointsForced.Where(point =>
|
||||
!point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
.ToList();
|
||||
@@ -642,7 +722,9 @@ public class LocationLootGenerator(
|
||||
|
||||
// Build the list of forced loot from both `spawnpointsForced` and any point marked `IsAlwaysSpawn`
|
||||
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.SpawnpointsForced);
|
||||
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.Spawnpoints.Where(point => point.Template.IsAlwaysSpawn ?? false));
|
||||
dynamicForcedSpawnPoints.AddRange(
|
||||
dynamicLootDist.Spawnpoints.Where(point => point.Template.IsAlwaysSpawn ?? false)
|
||||
);
|
||||
|
||||
// Add forced loot
|
||||
AddForcedLoot(loot, dynamicForcedSpawnPoints, locationName, staticAmmoDist);
|
||||
@@ -651,16 +733,19 @@ public class LocationLootGenerator(
|
||||
|
||||
// Draw from random distribution
|
||||
var desiredSpawnPointCount = Math.Round(
|
||||
GetLooseLootMultiplierForLocation(locationName) * _randomUtil.GetNormallyDistributedRandomNumber(
|
||||
(double) dynamicLootDist.SpawnpointCount.Mean,
|
||||
(double) dynamicLootDist.SpawnpointCount.Std
|
||||
)
|
||||
GetLooseLootMultiplierForLocation(locationName)
|
||||
* _randomUtil.GetNormallyDistributedRandomNumber(
|
||||
(double)dynamicLootDist.SpawnpointCount.Mean,
|
||||
(double)dynamicLootDist.SpawnpointCount.Std
|
||||
)
|
||||
);
|
||||
|
||||
// Positions not in forced but have 100% chance to spawn
|
||||
List<Spawnpoint> guaranteedLoosePoints = [];
|
||||
|
||||
var blacklistedSpawnPoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(locationName);
|
||||
var blacklistedSpawnPoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(
|
||||
locationName
|
||||
);
|
||||
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
|
||||
foreach (var spawnPoint in allDynamicSpawnPoints)
|
||||
@@ -689,7 +774,13 @@ public class LocationLootGenerator(
|
||||
continue;
|
||||
}
|
||||
|
||||
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(spawnPoint.Template.Id, spawnPoint.Probability ?? 0, spawnPoint));
|
||||
spawnPointArray.Add(
|
||||
new ProbabilityObject<string, Spawnpoint>(
|
||||
spawnPoint.Template.Id,
|
||||
spawnPoint.Probability ?? 0,
|
||||
spawnPoint
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Select a number of spawn points to add loot to
|
||||
@@ -700,16 +791,19 @@ 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
|
||||
// Add randomly chosen spawn points
|
||||
{
|
||||
foreach (var si in spawnPointArray.Draw((int) randomSpawnPointCount, false))
|
||||
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();
|
||||
chosenSpawnPoints = chosenSpawnPoints
|
||||
.GroupBy(spawnPoint => spawnPoint.LocationId)
|
||||
.Select(group => group.First())
|
||||
.ToList();
|
||||
|
||||
// Do we have enough items in pool to fulfill requirement
|
||||
var tooManySpawnPointsRequested = desiredSpawnPointCount - chosenSpawnPoints.Count > 0;
|
||||
@@ -724,7 +818,7 @@ public class LocationLootGenerator(
|
||||
{
|
||||
requested = desiredSpawnPointCount + guaranteedLoosePoints.Count,
|
||||
found = chosenSpawnPoints.Count,
|
||||
mapName = locationName
|
||||
mapName = locationName,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -740,22 +834,27 @@ public class LocationLootGenerator(
|
||||
if (spawnPoint.Template is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-missing_dynamic_template", spawnPoint.LocationId)
|
||||
_localisationService.GetText(
|
||||
"location-missing_dynamic_template",
|
||||
spawnPoint.LocationId
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(item => !_itemFilterService.IsLootableItemBlacklisted(item.Template)
|
||||
spawnPoint.Template.Items = spawnPoint
|
||||
.Template.Items.Where(item =>
|
||||
!_itemFilterService.IsLootableItemBlacklisted(item.Template)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
// 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)
|
||||
)
|
||||
spawnPoint.Template.Items = spawnPoint
|
||||
.Template.Items.Where(item => !seasonalItemTplBlacklist.Contains(item.Template))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -765,7 +864,10 @@ public class LocationLootGenerator(
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
_localisationService.GetText("location-spawnpoint_missing_items", spawnPoint.Template.Id)
|
||||
_localisationService.GetText(
|
||||
"location-spawnpoint_missing_items",
|
||||
spawnPoint.Template.Id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -784,13 +886,22 @@ public class LocationLootGenerator(
|
||||
continue;
|
||||
}
|
||||
|
||||
itemArray.Add(new ProbabilityObject<string, double?>(itemDist.ComposedKey.Key, itemDist.RelativeProbability ?? 0, null));
|
||||
itemArray.Add(
|
||||
new ProbabilityObject<string, double?>(
|
||||
itemDist.ComposedKey.Key,
|
||||
itemDist.RelativeProbability ?? 0,
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (itemArray.Count == 0)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-loot_pool_is_empty_skipping", spawnPoint.Template.Id)
|
||||
_localisationService.GetText(
|
||||
"location-loot_pool_is_empty_skipping",
|
||||
spawnPoint.Template.Id
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
@@ -822,44 +933,65 @@ public class LocationLootGenerator(
|
||||
/// <param name="lootLocationTemplates">List to add forced loot spawn locations to</param>
|
||||
/// <param name="forcedSpawnPoints">Forced loot locations that must be added</param>
|
||||
/// <param name="locationName">Name of map currently having force loot created for</param>
|
||||
protected void AddForcedLoot(List<SpawnpointTemplate> lootLocationTemplates,
|
||||
List<Spawnpoint> forcedSpawnPoints, string locationName,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist)
|
||||
protected void AddForcedLoot(
|
||||
List<SpawnpointTemplate> lootLocationTemplates,
|
||||
List<Spawnpoint> forcedSpawnPoints,
|
||||
string locationName,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist
|
||||
)
|
||||
{
|
||||
var lootToForceSingleAmountOnMap = _locationConfig.ForcedLootSingleSpawnById.GetValueOrDefault(locationName);
|
||||
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
|
||||
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
|
||||
{
|
||||
foreach (var itemTpl in lootToForceSingleAmountOnMap)
|
||||
{
|
||||
// Get all spawn positions for item tpl in forced loot array
|
||||
var items = forcedSpawnPoints.Where(forcedSpawnPoint => forcedSpawnPoint.Template.Items.FirstOrDefault().Template == itemTpl);
|
||||
var items = forcedSpawnPoints.Where(forcedSpawnPoint =>
|
||||
forcedSpawnPoint.Template.Items.FirstOrDefault().Template == itemTpl
|
||||
);
|
||||
if (!items.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to adjust loot item {itemTpl} as it does not exist inside {locationName} forced loot.");
|
||||
_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);
|
||||
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(
|
||||
_mathUtil,
|
||||
_cloner
|
||||
);
|
||||
foreach (var si in items)
|
||||
// use locationId as template.Id is the same across all items
|
||||
// use locationId as template.Id is the same across all items
|
||||
{
|
||||
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si));
|
||||
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))
|
||||
{
|
||||
var itemToAdd = items.FirstOrDefault(item => item.LocationId == spawnPointLocationId);
|
||||
var itemToAdd = items.FirstOrDefault(item =>
|
||||
item.LocationId == spawnPointLocationId
|
||||
);
|
||||
var lootItem = itemToAdd?.Template;
|
||||
if (lootItem is null)
|
||||
{
|
||||
_logger.Warning($"Item with spawn point id {spawnPointLocationId} could not be found, skipping");
|
||||
_logger.Warning(
|
||||
$"Item with spawn point id {spawnPointLocationId} could not be found, skipping"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -909,7 +1041,8 @@ public class LocationLootGenerator(
|
||||
forcedLootLocation.Template.Items = createItemResult.Items;
|
||||
|
||||
// Push forced location into array as long as it doesnt exist already
|
||||
var existingLocation = lootLocationTemplates.Any(spawnPoint => spawnPoint.Id == locationTemplateToAdd.Id
|
||||
var existingLocation = lootLocationTemplates.Any(spawnPoint =>
|
||||
spawnPoint.Id == locationTemplateToAdd.Id
|
||||
);
|
||||
if (!existingLocation)
|
||||
{
|
||||
@@ -934,13 +1067,19 @@ public class LocationLootGenerator(
|
||||
/// <param name="items"> Location loot Template </param>
|
||||
/// <param name="staticAmmoDist"> Ammo distributions </param>
|
||||
/// <returns> ContainerItem object </returns>
|
||||
protected ContainerItem CreateDynamicLootItem(string? chosenComposedKey, List<Item> items, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist)
|
||||
protected ContainerItem CreateDynamicLootItem(
|
||||
string? chosenComposedKey,
|
||||
List<Item> items,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist
|
||||
)
|
||||
{
|
||||
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");
|
||||
throw new Exception(
|
||||
$"Item for tpl {chosenComposedKey} was not found in the spawn point"
|
||||
);
|
||||
}
|
||||
|
||||
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
|
||||
@@ -958,48 +1097,34 @@ public class LocationLootGenerator(
|
||||
var stackCount =
|
||||
itemTemplate.Properties.StackMaxSize == 1
|
||||
? 1
|
||||
: _randomUtil.GetInt(itemTemplate.Properties.StackMinRandom.Value, itemTemplate.Properties.StackMaxRandom.Value);
|
||||
: _randomUtil.GetInt(
|
||||
itemTemplate.Properties.StackMinRandom.Value,
|
||||
itemTemplate.Properties.StackMaxRandom.Value
|
||||
);
|
||||
|
||||
itemWithMods.Add(
|
||||
new Item
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackCount
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = stackCount },
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
// Fill with cartridges
|
||||
List<Item> ammoBoxItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
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))
|
||||
{
|
||||
// Create array with just magazine
|
||||
List<Item> magazineItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl
|
||||
}
|
||||
];
|
||||
List<Item> magazineItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
|
||||
|
||||
if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent))
|
||||
// Add randomised amount of cartridges
|
||||
// Add randomised amount of cartridges
|
||||
{
|
||||
_itemHelper.FillMagazineWithRandomCartridge(
|
||||
magazineItem,
|
||||
@@ -1022,7 +1147,7 @@ public class LocationLootGenerator(
|
||||
itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren));
|
||||
|
||||
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template))
|
||||
// Strip children from parent before adding
|
||||
// Strip children from parent before adding
|
||||
{
|
||||
itemWithChildren = [itemWithChildren.FirstOrDefault()];
|
||||
}
|
||||
@@ -1037,16 +1162,16 @@ public class LocationLootGenerator(
|
||||
{
|
||||
Items = itemWithMods,
|
||||
Width = size.Width,
|
||||
Height = size.Height
|
||||
Height = size.Height,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// TODO: rewrite, BIG yikes
|
||||
protected ContainerItem? CreateStaticLootItem(
|
||||
string chosenTpl,
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
|
||||
string? parentId = null)
|
||||
string? parentId = null
|
||||
)
|
||||
{
|
||||
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
|
||||
if (itemTemplate.Properties is null)
|
||||
@@ -1058,14 +1183,7 @@ public class LocationLootGenerator(
|
||||
|
||||
var width = itemTemplate.Properties.Width;
|
||||
var height = itemTemplate.Properties.Height;
|
||||
List<Item> items =
|
||||
[
|
||||
new()
|
||||
{
|
||||
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
|
||||
@@ -1075,19 +1193,20 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
if (
|
||||
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY) ||
|
||||
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO)
|
||||
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY)
|
||||
|| _itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO)
|
||||
)
|
||||
{
|
||||
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
|
||||
var stackCount = itemTemplate.Properties.StackMaxSize == 1
|
||||
? 1
|
||||
: _randomUtil.GetInt(itemTemplate.Properties.StackMinRandom.Value, itemTemplate.Properties.StackMaxRandom.Value);
|
||||
var stackCount =
|
||||
itemTemplate.Properties.StackMaxSize == 1
|
||||
? 1
|
||||
: _randomUtil.GetInt(
|
||||
itemTemplate.Properties.StackMinRandom.Value,
|
||||
itemTemplate.Properties.StackMaxRandom.Value
|
||||
);
|
||||
|
||||
rootItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackCount
|
||||
};
|
||||
rootItem.Upd = new Upd { StackObjectsCount = stackCount };
|
||||
}
|
||||
// No spawn point, use default template
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
@@ -1120,11 +1239,16 @@ public class LocationLootGenerator(
|
||||
{
|
||||
Items = items,
|
||||
Width = width,
|
||||
Height = height
|
||||
Height = height,
|
||||
};
|
||||
}
|
||||
|
||||
protected List<Item> GetArmorItems(string chosenTpl, Item? rootItem, List<Item> items, TemplateItem armorDbTemplate)
|
||||
protected List<Item> GetArmorItems(
|
||||
string chosenTpl,
|
||||
Item? rootItem,
|
||||
List<Item> items,
|
||||
TemplateItem armorDbTemplate
|
||||
)
|
||||
{
|
||||
var defaultPreset = _presetHelper.GetDefaultPreset(chosenTpl);
|
||||
if (defaultPreset is not null)
|
||||
@@ -1163,9 +1287,10 @@ public class LocationLootGenerator(
|
||||
/// <returns>Root Item</returns>
|
||||
protected Item? CreateWeaponRootAndChildren(
|
||||
string chosenTpl,
|
||||
Dictionary<string,List<StaticAmmoDetails>> cartridgePool,
|
||||
Dictionary<string, List<StaticAmmoDetails>> cartridgePool,
|
||||
string? parentId,
|
||||
ref List<Item> items)
|
||||
ref List<Item> items
|
||||
)
|
||||
{
|
||||
List<Item> children = [];
|
||||
|
||||
@@ -1175,7 +1300,10 @@ public class LocationLootGenerator(
|
||||
{
|
||||
try
|
||||
{
|
||||
children = _itemHelper.ReparentItemAndChildren(defaultPreset.Items.FirstOrDefault(), defaultPreset.Items);
|
||||
children = _itemHelper.ReparentItemAndChildren(
|
||||
defaultPreset.Items.FirstOrDefault(),
|
||||
defaultPreset.Items
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1190,7 +1318,7 @@ public class LocationLootGenerator(
|
||||
tpl = chosenTpl,
|
||||
defaultId = defaultPreset.Id,
|
||||
defaultName = defaultPreset.Name,
|
||||
parentId
|
||||
parentId,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -1214,11 +1342,7 @@ public class LocationLootGenerator(
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"location-missing_root_item",
|
||||
new
|
||||
{
|
||||
tpl = chosenTpl,
|
||||
parentId
|
||||
}
|
||||
new { tpl = chosenTpl, parentId }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1237,11 +1361,7 @@ public class LocationLootGenerator(
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_reparent_item",
|
||||
new
|
||||
{
|
||||
tpl = chosenTpl,
|
||||
parentId
|
||||
}
|
||||
new { tpl = chosenTpl, parentId }
|
||||
)
|
||||
);
|
||||
_logger.Error(e.StackTrace);
|
||||
@@ -1285,7 +1405,8 @@ public class LocationLootGenerator(
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
|
||||
Item? rootItem,
|
||||
TemplateItem itemTemplate,
|
||||
List<Item> items)
|
||||
List<Item> items
|
||||
)
|
||||
{
|
||||
List<Item> magazineWithCartridges = [rootItem];
|
||||
_itemHelper.FillMagazineWithRandomCartridge(
|
||||
@@ -1305,40 +1426,20 @@ public class LocationLootGenerator(
|
||||
public record ContainerGroupCount
|
||||
{
|
||||
[JsonPropertyName("containerIdsWithProbability")]
|
||||
public Dictionary<string, double>? ContainerIdsWithProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public Dictionary<string, double>? ContainerIdsWithProbability { get; set; }
|
||||
|
||||
[JsonPropertyName("chosenCount")]
|
||||
public double? ChosenCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public double? ChosenCount { get; set; }
|
||||
}
|
||||
|
||||
public class ContainerItem
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public List<Item>? Items
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public List<Item>? Items { get; set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
public int? Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int? Width { get; set; }
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
public int? Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int? Height { get; set; }
|
||||
}
|
||||
|
||||
@@ -58,18 +58,16 @@ public class LootGenerator(
|
||||
{
|
||||
// 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
|
||||
result.Add(
|
||||
[
|
||||
new Item
|
||||
{
|
||||
StackObjectsCount = 1,
|
||||
SpawnedInSession = true
|
||||
}
|
||||
}
|
||||
]);
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenSealedContainer.Id,
|
||||
Upd = new Upd { StackObjectsCount = 1, SpawnedInSession = true },
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +83,21 @@ public class LootGenerator(
|
||||
// Pool has items we could add as loot, proceed
|
||||
if (rewardPoolResults.ItemPool.Count > 0)
|
||||
{
|
||||
var randomisedItemCount = _randomUtil.GetInt(options.ItemCount.Min, options.ItemCount.Max);
|
||||
var randomisedItemCount = _randomUtil.GetInt(
|
||||
options.ItemCount.Min,
|
||||
options.ItemCount.Max
|
||||
);
|
||||
for (var index = 0; index < randomisedItemCount; index++)
|
||||
{
|
||||
if (!FindAndAddRandomItemToLoot(rewardPoolResults.ItemPool, itemTypeCounts, options, result))
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
if (
|
||||
!FindAndAddRandomItemToLoot(
|
||||
rewardPoolResults.ItemPool,
|
||||
itemTypeCounts,
|
||||
options,
|
||||
result
|
||||
)
|
||||
)
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
@@ -105,9 +113,8 @@ public class LootGenerator(
|
||||
);
|
||||
if (randomisedWeaponPresetCount > 0)
|
||||
{
|
||||
var weaponDefaultPresets = globalDefaultPresets.Where(preset =>
|
||||
_itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON)
|
||||
)
|
||||
var weaponDefaultPresets = globalDefaultPresets
|
||||
.Where(preset => _itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON))
|
||||
.ToList();
|
||||
|
||||
if (weaponDefaultPresets.Any())
|
||||
@@ -115,14 +122,14 @@ public class LootGenerator(
|
||||
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
|
||||
)
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
@@ -140,9 +147,8 @@ public class LootGenerator(
|
||||
var armorDefaultPresets = globalDefaultPresets.Where(preset =>
|
||||
_itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia)
|
||||
);
|
||||
var levelFilteredArmorPresets = armorDefaultPresets.Where(armor =>
|
||||
IsArmorOfDesiredProtectionLevel(armor, options)
|
||||
)
|
||||
var levelFilteredArmorPresets = armorDefaultPresets
|
||||
.Where(armor => IsArmorOfDesiredProtectionLevel(armor, options))
|
||||
.ToList();
|
||||
|
||||
// Add some armors to rewards
|
||||
@@ -151,14 +157,14 @@ public class LootGenerator(
|
||||
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
|
||||
)
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
@@ -195,7 +201,9 @@ public class LootGenerator(
|
||||
for (var i = 0; i < randomisedItemCount; i++)
|
||||
{
|
||||
// Clone preset and alter Ids to be unique
|
||||
var presetWithUniqueIds = _itemHelper.ReplaceIDs(_cloner.Clone(preset.Items));
|
||||
var presetWithUniqueIds = _itemHelper.ReplaceIDs(
|
||||
_cloner.Clone(preset.Items)
|
||||
);
|
||||
|
||||
// Add to results
|
||||
result.Add(presetWithUniqueIds);
|
||||
@@ -203,7 +211,6 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// Non-preset item to be added
|
||||
@@ -211,11 +218,7 @@ public class LootGenerator(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = itemTpl,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = randomisedItemCount,
|
||||
SpawnedInSession = true
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = randomisedItemCount, SpawnedInSession = true },
|
||||
};
|
||||
var splitResults = _itemHelper.SplitStack(newLootItem);
|
||||
foreach (var splitItem in splitResults)
|
||||
@@ -242,11 +245,12 @@ public class LootGenerator(
|
||||
List<string> itemTypeWhitelist,
|
||||
bool useRewardItemBlacklist,
|
||||
bool allowBossItems,
|
||||
bool blockSeasonalItemsOutOfSeason)
|
||||
bool blockSeasonalItemsOutOfSeason
|
||||
)
|
||||
{
|
||||
var itemsDb = _databaseService.GetItems().Values;
|
||||
var itemBlacklist = new HashSet<string>();
|
||||
itemBlacklist.UnionWith([.._itemFilterService.GetBlacklistedItems(), ..itemTplBlacklist]);
|
||||
itemBlacklist.UnionWith([.. _itemFilterService.GetBlacklistedItems(), .. itemTplBlacklist]);
|
||||
|
||||
if (useRewardItemBlacklist)
|
||||
{
|
||||
@@ -256,10 +260,12 @@ public class LootGenerator(
|
||||
var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist();
|
||||
var itemsMatchingTypeBlacklist = itemsDb
|
||||
.Where(templateItem => !string.IsNullOrEmpty(templateItem.Parent)) // Ignore items without parents
|
||||
.Where(templateItem => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist))
|
||||
.Where(templateItem =>
|
||||
_itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist)
|
||||
)
|
||||
.Select(templateItem => templateItem.Id);
|
||||
|
||||
itemBlacklist.UnionWith([..rewardItemBlacklist, ..itemsMatchingTypeBlacklist]);
|
||||
itemBlacklist.UnionWith([.. rewardItemBlacklist, .. itemsMatchingTypeBlacklist]);
|
||||
}
|
||||
|
||||
if (!allowBossItems)
|
||||
@@ -272,19 +278,16 @@ public class LootGenerator(
|
||||
itemBlacklist.UnionWith(_seasonalEventService.GetInactiveSeasonalEventItems());
|
||||
}
|
||||
|
||||
var items = itemsDb.Where(item =>
|
||||
!itemBlacklist.Contains(item.Id) &&
|
||||
string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase) &&
|
||||
!item.Properties.QuestItem.GetValueOrDefault(false) &&
|
||||
itemTypeWhitelist.Contains(item.Parent)
|
||||
var items = itemsDb
|
||||
.Where(item =>
|
||||
!itemBlacklist.Contains(item.Id)
|
||||
&& string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase)
|
||||
&& !item.Properties.QuestItem.GetValueOrDefault(false)
|
||||
&& itemTypeWhitelist.Contains(item.Parent)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return new ItemRewardPoolResults
|
||||
{
|
||||
ItemPool = items,
|
||||
Blacklist = itemBlacklist
|
||||
};
|
||||
return new ItemRewardPoolResults { ItemPool = items, Blacklist = itemBlacklist };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -326,7 +329,7 @@ public class LootGenerator(
|
||||
itemTypeCounts[itemTypeId.Key] = new ItemLimit
|
||||
{
|
||||
Current = 0,
|
||||
Max = limits[itemTypeId.Key]
|
||||
Max = limits[itemTypeId.Key],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -341,13 +344,19 @@ public class LootGenerator(
|
||||
/// <param name="options">item filters</param>
|
||||
/// <param name="result">array to add found item to</param>
|
||||
/// <returns>true if item was valid and added to pool</returns>
|
||||
protected bool FindAndAddRandomItemToLoot(List<TemplateItem> items, Dictionary<string, ItemLimit> itemTypeCounts,
|
||||
protected bool FindAndAddRandomItemToLoot(
|
||||
List<TemplateItem> items,
|
||||
Dictionary<string, ItemLimit> itemTypeCounts,
|
||||
LootRequest options,
|
||||
List<List<Item>> result)
|
||||
List<List<Item>> result
|
||||
)
|
||||
{
|
||||
var randomItem = _randomUtil.GetArrayValue(items);
|
||||
|
||||
var itemLimitCount = itemTypeCounts.TryGetValue(randomItem.Parent, out var randomItemLimitCount);
|
||||
var itemLimitCount = itemTypeCounts.TryGetValue(
|
||||
randomItem.Parent,
|
||||
out var randomItemLimitCount
|
||||
);
|
||||
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max)
|
||||
{
|
||||
return false;
|
||||
@@ -363,11 +372,7 @@ public class LootGenerator(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = randomItem.Id,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1,
|
||||
SpawnedInSession = true
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = 1, SpawnedInSession = true },
|
||||
};
|
||||
|
||||
// Special case - handle items that need a stackcount > 1
|
||||
@@ -380,7 +385,7 @@ public class LootGenerator(
|
||||
result.Add([newLootItem]);
|
||||
|
||||
if (randomItemLimitCount is not null)
|
||||
// Increment item count as it's in limit array
|
||||
// Increment item count as it's in limit array
|
||||
{
|
||||
randomItemLimitCount.Current++;
|
||||
}
|
||||
@@ -417,10 +422,12 @@ public class LootGenerator(
|
||||
/// <param name="itemBlacklist">Items to skip</param>
|
||||
/// <param name="result">List to add chosen preset to</param>
|
||||
/// <returns>true if preset was valid and added to pool</returns>
|
||||
protected bool FindAndAddRandomPresetToLoot(List<Preset> presetPool,
|
||||
protected bool FindAndAddRandomPresetToLoot(
|
||||
List<Preset> presetPool,
|
||||
Dictionary<string, ItemLimit> itemTypeCounts,
|
||||
HashSet<string> itemBlacklist,
|
||||
List<List<Item>> result)
|
||||
List<List<Item>> result
|
||||
)
|
||||
{
|
||||
if (presetPool.Count == 0)
|
||||
{
|
||||
@@ -437,7 +444,12 @@ public class LootGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("loot-chosen_preset_missing_encyclopedia_value", chosenPreset?.Id));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"loot-chosen_preset_missing_encyclopedia_value",
|
||||
chosenPreset?.Id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -449,7 +461,9 @@ public class LootGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping");
|
||||
_logger.Debug(
|
||||
$"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping"
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -464,13 +478,21 @@ public class LootGenerator(
|
||||
// Some custom mod items lack a parent property
|
||||
if (itemDbDetails.Value?.Parent is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("loot-item_missing_parentid", itemDbDetails.Value?.Name));
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"loot-item_missing_parentid",
|
||||
itemDbDetails.Value?.Name
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check chosen preset hasn't exceeded spawn limit
|
||||
var hasItemLimitCount = itemTypeCounts.TryGetValue(itemDbDetails.Value.Parent, out var itemLimitCount);
|
||||
var hasItemLimitCount = itemTypeCounts.TryGetValue(
|
||||
itemDbDetails.Value.Parent,
|
||||
out var itemLimitCount
|
||||
);
|
||||
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max)
|
||||
{
|
||||
return false;
|
||||
@@ -485,7 +507,7 @@ public class LootGenerator(
|
||||
result.Add(presetAndMods);
|
||||
|
||||
if (itemLimitCount is not null)
|
||||
// Increment item count as item has been chosen and its inside itemLimitCount dictionary
|
||||
// Increment item count as item has been chosen and its inside itemLimitCount dictionary
|
||||
{
|
||||
itemLimitCount.Current++;
|
||||
}
|
||||
@@ -499,7 +521,9 @@ public class LootGenerator(
|
||||
/// </summary>
|
||||
/// <param name="containerSettings">sealed weapon container settings</param>
|
||||
/// <returns>List of items with children lists</returns>
|
||||
public List<List<Item>> GetSealedWeaponCaseLoot(SealedAirdropContainerSettings containerSettings)
|
||||
public List<List<Item>> GetSealedWeaponCaseLoot(
|
||||
SealedAirdropContainerSettings containerSettings
|
||||
)
|
||||
{
|
||||
List<List<Item>> itemsToReturn = [];
|
||||
|
||||
@@ -513,7 +537,10 @@ public class LootGenerator(
|
||||
if (!weaponDetailsDb.Key)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl)
|
||||
_localisationService.GetText(
|
||||
"loot-non_item_picked_as_sealed_weapon_crate_reward",
|
||||
chosenWeaponTpl
|
||||
)
|
||||
);
|
||||
|
||||
return itemsToReturn;
|
||||
@@ -528,9 +555,14 @@ public class LootGenerator(
|
||||
if (chosenWeaponPreset is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("loot-default_preset_not_found_using_random", chosenWeaponTpl)
|
||||
_localisationService.GetText(
|
||||
"loot-default_preset_not_found_using_random",
|
||||
chosenWeaponTpl
|
||||
)
|
||||
);
|
||||
chosenWeaponPreset = _randomUtil.GetArrayValue(
|
||||
_presetHelper.GetPresets(chosenWeaponTpl)
|
||||
);
|
||||
chosenWeaponPreset = _randomUtil.GetArrayValue(_presetHelper.GetPresets(chosenWeaponTpl));
|
||||
}
|
||||
|
||||
// Clean up Ids to ensure they're all unique and prevent collisions
|
||||
@@ -543,11 +575,17 @@ 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)
|
||||
GetSealedContainerWeaponModRewards(
|
||||
containerSettings,
|
||||
linkedItemsToWeapon,
|
||||
chosenWeaponPreset
|
||||
)
|
||||
);
|
||||
|
||||
// Handle non-weapon mod reward types
|
||||
itemsToReturn.AddRange(GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value));
|
||||
itemsToReturn.AddRange(
|
||||
GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value)
|
||||
);
|
||||
|
||||
return itemsToReturn;
|
||||
}
|
||||
@@ -558,8 +596,10 @@ public class LootGenerator(
|
||||
/// <param name="containerSettings">Sealed weapon container settings</param>
|
||||
/// <param name="weaponDetailsDb">Details for the weapon to reward player</param>
|
||||
/// <returns>List of item with children lists</returns>
|
||||
protected List<List<Item>> GetSealedContainerNonWeaponModRewards(SealedAirdropContainerSettings containerSettings,
|
||||
TemplateItem weaponDetailsDb)
|
||||
protected List<List<Item>> GetSealedContainerNonWeaponModRewards(
|
||||
SealedAirdropContainerSettings containerSettings,
|
||||
TemplateItem weaponDetailsDb
|
||||
)
|
||||
{
|
||||
List<List<Item>> rewards = [];
|
||||
|
||||
@@ -576,11 +616,10 @@ public class LootGenerator(
|
||||
{
|
||||
// Get ammo boxes from db
|
||||
var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select(tpl =>
|
||||
{
|
||||
var itemDetails = _itemHelper.GetItem(tpl);
|
||||
return itemDetails.Value;
|
||||
}
|
||||
);
|
||||
{
|
||||
var itemDetails = _itemHelper.GetItem(tpl);
|
||||
return itemDetails.Value;
|
||||
});
|
||||
|
||||
// Need to find boxes that matches weapons caliber
|
||||
var weaponCaliber = weaponDetailsDb.Properties.AmmoCaliber;
|
||||
@@ -602,11 +641,7 @@ public class LootGenerator(
|
||||
var chosenAmmoBox = _randomUtil.GetArrayValue(ammoBoxesMatchingCaliber);
|
||||
var ammoBoxReward = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenAmmoBox.Id
|
||||
}
|
||||
new() { Id = _hashUtil.Generate(), Template = chosenAmmoBox.Id },
|
||||
};
|
||||
_itemHelper.AddCartridgesToAmmoBox(ammoBoxReward, chosenAmmoBox);
|
||||
rewards.Add(ammoBoxReward);
|
||||
@@ -616,13 +651,14 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
// Get all items of the desired type + not quest items + not globally blacklisted
|
||||
var rewardItemPool = _databaseService.GetItems()
|
||||
var rewardItemPool = _databaseService
|
||||
.GetItems()
|
||||
.Values.Where(item =>
|
||||
item.Parent == rewardKey &&
|
||||
string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase) &&
|
||||
_itemFilterService.IsItemBlacklisted(item.Id) &&
|
||||
!(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id)) &&
|
||||
item.Properties.QuestItem is null
|
||||
item.Parent == rewardKey
|
||||
&& string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase)
|
||||
&& _itemFilterService.IsItemBlacklisted(item.Id)
|
||||
&& !(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id))
|
||||
&& item.Properties.QuestItem is null
|
||||
);
|
||||
|
||||
if (!rewardItemPool.Any())
|
||||
@@ -641,11 +677,7 @@ public class LootGenerator(
|
||||
var chosenRewardItem = _randomUtil.GetArrayValue(rewardItemPool);
|
||||
var rewardItem = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenRewardItem.Id
|
||||
}
|
||||
new() { Id = _hashUtil.Generate(), Template = chosenRewardItem.Id },
|
||||
};
|
||||
|
||||
rewards.Add(rewardItem);
|
||||
@@ -662,8 +694,11 @@ public class LootGenerator(
|
||||
/// <param name="linkedItemsToWeapon">All items that can be attached/inserted into weapon</param>
|
||||
/// <param name="chosenWeaponPreset">The weapon preset given to player as reward</param>
|
||||
/// <returns>List of item with children lists</returns>
|
||||
protected List<List<Item>> GetSealedContainerWeaponModRewards(SealedAirdropContainerSettings containerSettings, List<TemplateItem> linkedItemsToWeapon,
|
||||
Preset chosenWeaponPreset)
|
||||
protected List<List<Item>> GetSealedContainerWeaponModRewards(
|
||||
SealedAirdropContainerSettings containerSettings,
|
||||
List<TemplateItem> linkedItemsToWeapon,
|
||||
Preset chosenWeaponPreset
|
||||
)
|
||||
{
|
||||
List<List<Item>> modRewards = [];
|
||||
|
||||
@@ -678,7 +713,8 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
// 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)
|
||||
var relatedItems = linkedItemsToWeapon?.Where(item =>
|
||||
item?.Parent == rewardKey && !_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
);
|
||||
if (relatedItems is null || !relatedItems.Any())
|
||||
{
|
||||
@@ -698,11 +734,7 @@ public class LootGenerator(
|
||||
var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList());
|
||||
var reward = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenItem[0].Id
|
||||
}
|
||||
new() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id },
|
||||
};
|
||||
|
||||
modRewards.Add(reward);
|
||||
@@ -742,11 +774,7 @@ public class LootGenerator(
|
||||
|
||||
List<Item> rewardItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenRewardItemTpl
|
||||
}
|
||||
new() { Id = _hashUtil.Generate(), Template = chosenRewardItemTpl },
|
||||
];
|
||||
itemsToReturn.Add(rewardItem);
|
||||
}
|
||||
@@ -761,47 +789,33 @@ 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, false)
|
||||
.ItemPool.Select(item => item.Id
|
||||
)
|
||||
.ItemPool.Select(item => item.Id)
|
||||
);
|
||||
}
|
||||
|
||||
public record ItemRewardPoolResults
|
||||
{
|
||||
public List<TemplateItem> ItemPool
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public List<TemplateItem> ItemPool { get; set; }
|
||||
|
||||
public HashSet<string> Blacklist
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public HashSet<string> Blacklist { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemLimit
|
||||
{
|
||||
[JsonPropertyName("current")]
|
||||
public int Current
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int Current { get; set; }
|
||||
|
||||
[JsonPropertyName("max")]
|
||||
public int Max
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int Max { get; set; }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ public class PMCLootGenerator(
|
||||
RagfairPriceService ragfairPriceService,
|
||||
SeasonalEventService seasonalEventService,
|
||||
WeightedRandomHelper weightedRandomHelper,
|
||||
ConfigServer configServer)
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
private readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
|
||||
|
||||
@@ -53,7 +54,12 @@ public class PMCLootGenerator(
|
||||
var blacklist = GetContainerLootBlacklist();
|
||||
|
||||
// Generate loot and cache - Also pass check to ensure only 1x2 items are allowed (Unheard bots have big pockets, hence the need for 1x2)
|
||||
var pool = GenerateLootPool(pmcRole, allowedItemTypeWhitelist, blacklist, ItemFitsInto1By2Slot);
|
||||
var pool = GenerateLootPool(
|
||||
pmcRole,
|
||||
allowedItemTypeWhitelist,
|
||||
blacklist,
|
||||
ItemFitsInto1By2Slot
|
||||
);
|
||||
_pocketLootPool.TryAdd(pmcRole, pool);
|
||||
|
||||
return pool;
|
||||
@@ -83,7 +89,12 @@ public class PMCLootGenerator(
|
||||
blacklist.UnionWith(_pmcConfig.VestLoot.Blacklist); // Include vest-specific blacklist
|
||||
|
||||
// Generate loot and cache - Also pass check to ensure items up to 2x2 are allowed, some vests have big slots
|
||||
var pool = GenerateLootPool(pmcRole, allowedItemTypeWhitelist, blacklist, ItemFitsInto2By2Slot);
|
||||
var pool = GenerateLootPool(
|
||||
pmcRole,
|
||||
allowedItemTypeWhitelist,
|
||||
blacklist,
|
||||
ItemFitsInto2By2Slot
|
||||
);
|
||||
_vestLootPool.TryAdd(pmcRole, pool);
|
||||
|
||||
return pool;
|
||||
@@ -99,7 +110,6 @@ public class PMCLootGenerator(
|
||||
{
|
||||
lock (BackpackLock)
|
||||
{
|
||||
|
||||
// Already exists, return values
|
||||
if (_backpackLootPool.TryGetValue(pmcRole, out var existingLootPool))
|
||||
{
|
||||
@@ -126,7 +136,12 @@ public class PMCLootGenerator(
|
||||
/// <param name="itemTplAndParentBlacklist">Item and parent blacklist</param>
|
||||
/// <param name="genericItemCheck">An optional delegate to validate the TemplateItem object being processed</param>
|
||||
/// <returns>Dictionary of items and weights inversely tied to the items price</returns>
|
||||
protected Dictionary<string, double> GenerateLootPool(string pmcRole, HashSet<string> allowedItemTypeWhitelist, HashSet<string> itemTplAndParentBlacklist, Func<TemplateItem, bool>? genericItemCheck)
|
||||
protected Dictionary<string, double> GenerateLootPool(
|
||||
string pmcRole,
|
||||
HashSet<string> allowedItemTypeWhitelist,
|
||||
HashSet<string> itemTplAndParentBlacklist,
|
||||
Func<TemplateItem, bool>? genericItemCheck
|
||||
)
|
||||
{
|
||||
var lootPool = new Dictionary<string, double>();
|
||||
var items = databaseService.GetItems();
|
||||
@@ -136,13 +151,15 @@ public class PMCLootGenerator(
|
||||
|
||||
// Filter all items in DB to ones we want with passed in whitelist + blacklist + generic 'IsValidItem' check
|
||||
// Also run Delegate if it's not null
|
||||
var itemTplsToAdd = items.Where(item =>
|
||||
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
|
||||
itemHelper.IsValidItem(item.Value.Id) &&
|
||||
!itemTplAndParentBlacklist.Contains(item.Value.Id) &&
|
||||
!itemTplAndParentBlacklist.Contains(item.Value.Parent) &&
|
||||
(genericItemCheck?.Invoke(item.Value) ?? true) // if delegate is null, force check to be true
|
||||
).Select(x => x.Key);
|
||||
var itemTplsToAdd = items
|
||||
.Where(item =>
|
||||
allowedItemTypeWhitelist.Contains(item.Value.Parent)
|
||||
&& itemHelper.IsValidItem(item.Value.Id)
|
||||
&& !itemTplAndParentBlacklist.Contains(item.Value.Id)
|
||||
&& !itemTplAndParentBlacklist.Contains(item.Value.Parent)
|
||||
&& (genericItemCheck?.Invoke(item.Value) ?? true) // if delegate is null, force check to be true
|
||||
)
|
||||
.Select(x => x.Key);
|
||||
|
||||
// Store all items + price in above lootPool dictionary
|
||||
foreach (var tpl in itemTplsToAdd)
|
||||
@@ -202,7 +219,6 @@ public class PMCLootGenerator(
|
||||
logger.Error($"Unable to find price overrides for PMC: {pmcRole}");
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,7 +229,10 @@ public class PMCLootGenerator(
|
||||
/// <returns>Rouble price</returns>
|
||||
protected double GetItemPrice(string tpl, Dictionary<string, double>? pmcPriceOverrides = null)
|
||||
{
|
||||
if (pmcPriceOverrides is not null && pmcPriceOverrides.TryGetValue(tpl, out var overridePrice))
|
||||
if (
|
||||
pmcPriceOverrides is not null
|
||||
&& pmcPriceOverrides.TryGetValue(tpl, out var overridePrice)
|
||||
)
|
||||
{
|
||||
// There's a price override for this item, use override instead of default price
|
||||
return overridePrice;
|
||||
@@ -245,7 +264,7 @@ public class PMCLootGenerator(
|
||||
return $"{item.Properties.Width}x{item.Properties.Height}" switch
|
||||
{
|
||||
"1x1" or "1x2" or "2x1" => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ using SPTarkov.Server.Core.Utils.Cloners;
|
||||
using SPTarkov.Server.Core.Utils.Json;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
namespace SPTarkov.Server.Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
@@ -54,9 +53,16 @@ public class PlayerScavGenerator(
|
||||
var scavKarmaLevel = GetScavKarmaLevel(pmcDataClone);
|
||||
|
||||
// use karma level to get correct karmaSettings
|
||||
if (!_playerScavConfig.KarmaLevel.TryGetValue(scavKarmaLevel.ToString(), out var playerScavKarmaSettings))
|
||||
if (
|
||||
!_playerScavConfig.KarmaLevel.TryGetValue(
|
||||
scavKarmaLevel.ToString(),
|
||||
out var playerScavKarmaSettings
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel));
|
||||
_logger.Error(
|
||||
_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel)
|
||||
);
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
@@ -101,24 +107,20 @@ public class PlayerScavGenerator(
|
||||
scavData.Info.Level = GetScavLevel(existingScavDataClone);
|
||||
scavData.Info.Experience = GetScavExperience(existingScavDataClone);
|
||||
scavData.Quests = existingScavDataClone.Quests ?? [];
|
||||
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.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(
|
||||
playerScavKarmaSettings.LootItemsToAddChancePercent,
|
||||
scavData,
|
||||
[
|
||||
EquipmentSlots.TacticalVest,
|
||||
EquipmentSlots.Pockets,
|
||||
EquipmentSlots.Backpack
|
||||
]
|
||||
[EquipmentSlots.TacticalVest, EquipmentSlots.Pockets, EquipmentSlots.Backpack]
|
||||
);
|
||||
|
||||
// Remove secure container
|
||||
@@ -139,8 +141,11 @@ public class PlayerScavGenerator(
|
||||
/// <param name="possibleItemsToAdd">dict of tpl + % chance to be added</param>
|
||||
/// <param name="scavData"></param>
|
||||
/// <param name="containersToAddTo">Possible slotIds to add loot to</param>
|
||||
protected void AddAdditionalLootToPlayerScavContainers(Dictionary<string, double> possibleItemsToAdd, BotBase scavData,
|
||||
HashSet<EquipmentSlots> containersToAddTo)
|
||||
protected void AddAdditionalLootToPlayerScavContainers(
|
||||
Dictionary<string, double> possibleItemsToAdd,
|
||||
BotBase scavData,
|
||||
HashSet<EquipmentSlots> containersToAddTo
|
||||
)
|
||||
{
|
||||
foreach (var tpl in possibleItemsToAdd)
|
||||
{
|
||||
@@ -153,7 +158,9 @@ public class PlayerScavGenerator(
|
||||
var itemResult = _itemHelper.GetItem(tpl.Key);
|
||||
if (!itemResult.Key)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("scav-unable_to_add_item_to_player_scav", tpl));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("scav-unable_to_add_item_to_player_scav", tpl)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -164,8 +171,8 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = itemTemplate.Id,
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate)
|
||||
}
|
||||
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate),
|
||||
},
|
||||
};
|
||||
|
||||
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
|
||||
@@ -197,7 +204,9 @@ public class PlayerScavGenerator(
|
||||
// can be empty during profile creation
|
||||
if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo))
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("scav-missing_karma_level_getting_default"));
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("scav-missing_karma_level_getting_default")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -239,7 +248,10 @@ public class PlayerScavGenerator(
|
||||
/// </summary>
|
||||
/// <param name="karmaSettings">Values to modify the bot template with</param>
|
||||
/// <param name="baseBotNode">bot template to modify according to karama level settings</param>
|
||||
protected void AdjustBotTemplateWithKarmaSpecificSettings(KarmaLevel karmaSettings, BotType baseBotNode)
|
||||
protected void AdjustBotTemplateWithKarmaSpecificSettings(
|
||||
KarmaLevel karmaSettings,
|
||||
BotType baseBotNode
|
||||
)
|
||||
{
|
||||
// Adjust equipment chance values
|
||||
foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment)
|
||||
@@ -251,8 +263,13 @@ public class PlayerScavGenerator(
|
||||
}
|
||||
|
||||
// Try add new key with value
|
||||
if (!baseBotNode.BotChances.EquipmentChances.TryAdd(equipmentKvP.Key, equipmentKvP.Value))
|
||||
// Unable to add new, update existing
|
||||
if (
|
||||
!baseBotNode.BotChances.EquipmentChances.TryAdd(
|
||||
equipmentKvP.Key,
|
||||
equipmentKvP.Value
|
||||
)
|
||||
)
|
||||
// Unable to add new, update existing
|
||||
{
|
||||
baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value;
|
||||
}
|
||||
@@ -272,7 +289,6 @@ public class PlayerScavGenerator(
|
||||
baseBotNode.BotChances.WeaponModsChances.TryAdd(modKvP.Key, 0);
|
||||
baseBotNode.BotChances.WeaponModsChances[modKvP.Key] += value;
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
@@ -280,14 +296,19 @@ public class PlayerScavGenerator(
|
||||
var props = baseBotNode.BotGeneration.Items.GetType().GetProperties();
|
||||
foreach (var itemLimitKvP in karmaSettings.ItemLimits)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => string.Equals(x.Name, itemLimitKvP.Key, StringComparison.OrdinalIgnoreCase));
|
||||
var prop = props.FirstOrDefault(x =>
|
||||
string.Equals(x.Name, itemLimitKvP.Key, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
prop.SetValue(baseBotNode.BotGeneration.Items, itemLimitKvP.Value);
|
||||
}
|
||||
|
||||
// Blacklist equipment, keyed by equipment slot
|
||||
foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist)
|
||||
{
|
||||
baseBotNode.BotInventory.Equipment.TryGetValue(equipmentBlacklistKvP.Key, out var equipmentDict);
|
||||
baseBotNode.BotInventory.Equipment.TryGetValue(
|
||||
equipmentBlacklistKvP.Key,
|
||||
out var equipmentDict
|
||||
);
|
||||
foreach (var itemToRemove in equipmentBlacklistKvP.Value)
|
||||
{
|
||||
equipmentDict.Remove(itemToRemove);
|
||||
@@ -311,7 +332,7 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
Common = [],
|
||||
Mastering = [],
|
||||
Points = 0
|
||||
Points = 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -357,18 +378,26 @@ public class PlayerScavGenerator(
|
||||
protected PmcData SetScavCooldownTimer(PmcData scavData, PmcData pmcData)
|
||||
{
|
||||
// Get sum of all scav cooldown reduction timer bonuses
|
||||
var modifier = 1d + pmcData.Bonuses
|
||||
.Where(x => x.Type == BonusType.ScavCooldownTimer)
|
||||
.Sum(bonus => (bonus?.Value ?? 1) / 100);
|
||||
var modifier =
|
||||
1d
|
||||
+ pmcData
|
||||
.Bonuses.Where(x => x.Type == BonusType.ScavCooldownTimer)
|
||||
.Sum(bonus => (bonus?.Value ?? 1) / 100);
|
||||
|
||||
var fenceInfo = _fenceService.GetFenceInfo(pmcData);
|
||||
modifier *= fenceInfo.SavageCooldownModifier ?? 1d;
|
||||
|
||||
// Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it.
|
||||
var scavLockDuration = _databaseService.GetGlobals().Configuration.SavagePlayCooldown * modifier;
|
||||
var scavLockDuration =
|
||||
_databaseService.GetGlobals().Configuration.SavagePlayCooldown * modifier;
|
||||
|
||||
var fullProfile = _profileHelper.GetFullProfile(pmcData?.SessionId);
|
||||
if (fullProfile?.ProfileInfo?.Edition?.StartsWith(AccountTypes.SPT_DEVELOPER, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
if (
|
||||
fullProfile?.ProfileInfo?.Edition?.StartsWith(
|
||||
AccountTypes.SPT_DEVELOPER,
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
) ?? false
|
||||
)
|
||||
{
|
||||
// Force lock duration to 10seconds for dev profiles
|
||||
scavLockDuration = 10;
|
||||
@@ -376,7 +405,9 @@ public class PlayerScavGenerator(
|
||||
|
||||
if (scavData?.Info != null)
|
||||
{
|
||||
scavData.Info.SavageLockTime = Math.Round(_timeUtil.GetTimeStamp() + (scavLockDuration ?? 0));
|
||||
scavData.Info.SavageLockTime = Math.Round(
|
||||
_timeUtil.GetTimeStamp() + (scavLockDuration ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
return scavData;
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace SPTarkov.Server.Core.Generators;
|
||||
public class PmcWaveGenerator(
|
||||
ISptLogger<PmcWaveGenerator> logger,
|
||||
DatabaseService databaseService,
|
||||
ConfigServer configServer)
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
protected readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class RagfairAssortGenerator(
|
||||
BaseClasses.INVENTORY,
|
||||
BaseClasses.STATIONARY_CONTAINER,
|
||||
BaseClasses.POCKETS,
|
||||
BaseClasses.BUILT_IN_INSERTS
|
||||
BaseClasses.BUILT_IN_INSERTS,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +53,9 @@ public class RagfairAssortGenerator(
|
||||
List<List<Item>> results = [];
|
||||
|
||||
// Get cloned items from db
|
||||
var dbItemsClone = itemHelper.GetItems().Where(item => !string.Equals(item.Type, "Node", StringComparison.OrdinalIgnoreCase));
|
||||
var dbItemsClone = itemHelper
|
||||
.GetItems()
|
||||
.Where(item => !string.Equals(item.Type, "Node", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Store processed preset tpls so we don't add them when processing non-preset items
|
||||
HashSet<string> processedArmorItems = [];
|
||||
@@ -76,7 +78,7 @@ public class RagfairAssortGenerator(
|
||||
{
|
||||
StackObjectsCount = 99999999,
|
||||
UnlimitedCount = true,
|
||||
SptPresetId = preset.Id
|
||||
SptPresetId = preset.Id,
|
||||
};
|
||||
|
||||
results.Add(presetAndMods);
|
||||
@@ -91,24 +93,21 @@ public class RagfairAssortGenerator(
|
||||
|
||||
// Skip seasonal items when not in-season
|
||||
if (
|
||||
RagfairConfig.Dynamic.RemoveSeasonalItemsWhenNotInEvent &&
|
||||
!seasonalEventActive &&
|
||||
seasonalItemTplBlacklist.Contains(item.Id)
|
||||
RagfairConfig.Dynamic.RemoveSeasonalItemsWhenNotInEvent
|
||||
&& !seasonalEventActive
|
||||
&& seasonalItemTplBlacklist.Contains(item.Id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (processedArmorItems.Contains(item.Id))
|
||||
// Already processed
|
||||
// Already processed
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ragfairAssort = CreateRagfairAssortRootItem(
|
||||
item.Id,
|
||||
item.Id
|
||||
); // tpl and id must be the same so hideout recipe rewards work
|
||||
var ragfairAssort = CreateRagfairAssortRootItem(item.Id, item.Id); // tpl and id must be the same so hideout recipe rewards work
|
||||
|
||||
results.Add([ragfairAssort]);
|
||||
}
|
||||
@@ -147,11 +146,7 @@ public class RagfairAssortGenerator(
|
||||
Template = tplId,
|
||||
ParentId = "hideout",
|
||||
SlotId = "hideout",
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 99999999,
|
||||
UnlimitedCount = true
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = 99999999, UnlimitedCount = true },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,15 @@ public class RagfairOfferGenerator(
|
||||
bool sellInOnePiece = false
|
||||
)
|
||||
{
|
||||
var offer = CreateOffer(userId, time, items, barterScheme, loyalLevel, quantity, sellInOnePiece);
|
||||
var offer = CreateOffer(
|
||||
userId,
|
||||
time,
|
||||
items,
|
||||
barterScheme,
|
||||
loyalLevel,
|
||||
quantity,
|
||||
sellInOnePiece
|
||||
);
|
||||
ragfairOfferService.AddOffer(offer);
|
||||
|
||||
return offer;
|
||||
@@ -95,25 +103,25 @@ public class RagfairOfferGenerator(
|
||||
bool isPackOffer = false
|
||||
)
|
||||
{
|
||||
var offerRequirements = barterScheme.Select(barter =>
|
||||
var offerRequirements = barterScheme
|
||||
.Select(barter =>
|
||||
{
|
||||
var offerRequirement = new OfferRequirement
|
||||
{
|
||||
var offerRequirement = new OfferRequirement
|
||||
{
|
||||
Template = barter.Template,
|
||||
Count = Math.Round(barter.Count.Value, 2),
|
||||
OnlyFunctional = barter.OnlyFunctional ?? false
|
||||
};
|
||||
Template = barter.Template,
|
||||
Count = Math.Round(barter.Count.Value, 2),
|
||||
OnlyFunctional = barter.OnlyFunctional ?? false,
|
||||
};
|
||||
|
||||
// Dogtags define level and side
|
||||
if (barter.Level != null)
|
||||
{
|
||||
offerRequirement.Level = barter.Level;
|
||||
offerRequirement.Side = barter.Side;
|
||||
}
|
||||
|
||||
return offerRequirement;
|
||||
// Dogtags define level and side
|
||||
if (barter.Level != null)
|
||||
{
|
||||
offerRequirement.Level = barter.Level;
|
||||
offerRequirement.Side = barter.Side;
|
||||
}
|
||||
)
|
||||
|
||||
return offerRequirement;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Clone to avoid modifying original array
|
||||
@@ -122,13 +130,21 @@ 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(rootItem.Template).Value);
|
||||
itemHelper.AddCartridgesToAmmoBox(
|
||||
itemsClone,
|
||||
itemHelper.GetItem(rootItem.Template).Value
|
||||
);
|
||||
}
|
||||
|
||||
var roubleListingPrice = Math.Round(ConvertOfferRequirementsIntoRoubles(offerRequirements));
|
||||
var singleItemListingPrice = isPackOffer ? roubleListingPrice / quantity : roubleListingPrice;
|
||||
var singleItemListingPrice = isPackOffer
|
||||
? roubleListingPrice / quantity
|
||||
: roubleListingPrice;
|
||||
|
||||
var offer = new RagfairOffer
|
||||
{
|
||||
@@ -146,7 +162,7 @@ public class RagfairOfferGenerator(
|
||||
LoyaltyLevel = loyalLevel,
|
||||
SellInOnePiece = isPackOffer,
|
||||
Locked = false,
|
||||
Quantity = quantity
|
||||
Quantity = quantity,
|
||||
};
|
||||
|
||||
offerCounter++;
|
||||
@@ -165,11 +181,7 @@ public class RagfairOfferGenerator(
|
||||
// Trader offer
|
||||
if (isTrader)
|
||||
{
|
||||
return new RagfairOfferUser
|
||||
{
|
||||
Id = userId,
|
||||
MemberType = MemberCategory.Trader
|
||||
};
|
||||
return new RagfairOfferUser { Id = userId, MemberType = MemberCategory.Trader };
|
||||
}
|
||||
|
||||
var isPlayerOffer = profileHelper.IsPlayer(userId);
|
||||
@@ -185,7 +197,7 @@ public class RagfairOfferGenerator(
|
||||
Rating = playerProfile.RagfairInfo.Rating ?? 0,
|
||||
IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing,
|
||||
Avatar = null,
|
||||
Aid = playerProfile.Aid
|
||||
Aid = playerProfile.Aid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,7 +213,7 @@ public class RagfairOfferGenerator(
|
||||
),
|
||||
IsRatingGrowing = randomUtil.GetBool(),
|
||||
Avatar = null,
|
||||
Aid = hashUtil.GenerateAccountId()
|
||||
Aid = hashUtil.GenerateAccountId(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,14 +222,17 @@ public class RagfairOfferGenerator(
|
||||
/// </summary>
|
||||
/// <param name="offerRequirements"> barter requirements for offer </param>
|
||||
/// <returns> rouble cost of offer </returns>
|
||||
protected double ConvertOfferRequirementsIntoRoubles(IEnumerable<OfferRequirement> offerRequirements)
|
||||
protected double ConvertOfferRequirementsIntoRoubles(
|
||||
IEnumerable<OfferRequirement> offerRequirements
|
||||
)
|
||||
{
|
||||
var roublePrice = 0d;
|
||||
foreach (var requirement in offerRequirements)
|
||||
{
|
||||
roublePrice += paymentHelper.IsMoneyTpl(requirement.Template)
|
||||
? Math.Round(CalculateRoublePrice(requirement.Count.Value, requirement.Template))
|
||||
: ragfairPriceService.GetFleaPriceForItem(requirement.Template) * requirement.Count.Value; // Get flea price for barter offer items
|
||||
: ragfairPriceService.GetFleaPriceForItem(requirement.Template)
|
||||
* requirement.Count.Value; // Get flea price for barter offer items
|
||||
}
|
||||
|
||||
return roublePrice;
|
||||
@@ -290,7 +305,10 @@ public class RagfairOfferGenerator(
|
||||
}
|
||||
|
||||
// Generated pmc offer
|
||||
return randomUtil.GetDouble(ragfairConfig.Dynamic.Rating.Min, ragfairConfig.Dynamic.Rating.Max);
|
||||
return randomUtil.GetDouble(
|
||||
ragfairConfig.Dynamic.Rating.Min,
|
||||
ragfairConfig.Dynamic.Rating.Max
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -301,13 +319,15 @@ public class RagfairOfferGenerator(
|
||||
protected bool GetRatingGrowing(string userID)
|
||||
{
|
||||
if (profileHelper.IsPlayer(userID))
|
||||
// player offer
|
||||
// player offer
|
||||
{
|
||||
return saveServer.GetProfile(userID).CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
|
||||
return saveServer
|
||||
.GetProfile(userID)
|
||||
.CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
|
||||
}
|
||||
|
||||
if (ragfairServerHelper.IsTrader(userID))
|
||||
// trader offer
|
||||
// trader offer
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -328,20 +348,30 @@ public class RagfairOfferGenerator(
|
||||
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));
|
||||
var offerDurationTimeHours = databaseService
|
||||
.GetGlobals()
|
||||
.Configuration.RagFair.OfferDurationTimeInHour;
|
||||
return (long)(
|
||||
timeUtil.GetTimeStamp()
|
||||
+ Math.Round((double)offerDurationTimeHours * TimeUtil.OneHourAsSeconds)
|
||||
);
|
||||
}
|
||||
|
||||
if (ragfairServerHelper.IsTrader(userID))
|
||||
// Trader offer
|
||||
// Trader offer
|
||||
{
|
||||
return (long) databaseService.GetTrader(userID).Base.NextResupply;
|
||||
return (long)databaseService.GetTrader(userID).Base.NextResupply;
|
||||
}
|
||||
|
||||
// Generated fake-player offer
|
||||
return (long) Math.Round(
|
||||
time + randomUtil.GetDouble(ragfairConfig.Dynamic.EndTimeSeconds.Min, ragfairConfig.Dynamic.EndTimeSeconds.Max)
|
||||
);
|
||||
return (long)
|
||||
Math.Round(
|
||||
time
|
||||
+ randomUtil.GetDouble(
|
||||
ragfairConfig.Dynamic.EndTimeSeconds.Min,
|
||||
ragfairConfig.Dynamic.EndTimeSeconds.Max
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -360,7 +390,9 @@ public class RagfairOfferGenerator(
|
||||
stopwatch.Stop();
|
||||
if (logger.IsLogEnabled(LogLevel.Debug) && stopwatch.ElapsedMilliseconds > 0)
|
||||
{
|
||||
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts - {assortItemsToProcess.Count} items");
|
||||
logger.Debug(
|
||||
$"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts - {assortItemsToProcess.Count} items"
|
||||
);
|
||||
}
|
||||
|
||||
stopwatch.Restart();
|
||||
@@ -369,10 +401,13 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic);
|
||||
}
|
||||
)
|
||||
{
|
||||
CreateOffersFromAssort(
|
||||
assortItem,
|
||||
replacingExpiredOffers,
|
||||
ragfairConfig.Dynamic
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -408,7 +443,10 @@ public class RagfairOfferGenerator(
|
||||
// Armor presets can hold plates above the allowed flea level, remove if necessary
|
||||
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList)
|
||||
{
|
||||
RemoveBannedPlatesFromPreset(assortItemWithChildren, ragfairConfig.Dynamic.Blacklist.ArmorPlate);
|
||||
RemoveBannedPlatesFromPreset(
|
||||
assortItemWithChildren,
|
||||
ragfairConfig.Dynamic.Blacklist.ArmorPlate
|
||||
);
|
||||
}
|
||||
|
||||
// Get number of offers to create
|
||||
@@ -427,7 +465,12 @@ public class RagfairOfferGenerator(
|
||||
clonedAssort[0].ParentId = null;
|
||||
clonedAssort[0].SlotId = null;
|
||||
|
||||
CreateSingleOfferForItem(hashUtil.Generate(), clonedAssort, isPreset, itemToSellDetails.Value);
|
||||
CreateSingleOfferForItem(
|
||||
hashUtil.Generate(),
|
||||
clonedAssort,
|
||||
isPreset,
|
||||
itemToSellDetails.Value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,14 +486,16 @@ public class RagfairOfferGenerator(
|
||||
)
|
||||
{
|
||||
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template))
|
||||
// Cant hold armor inserts, skip
|
||||
// Cant hold armor inserts, skip
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var plateSlots = presetWithChildren.Where(item => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())).ToList();
|
||||
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
|
||||
// Has no plate slots e.g. "front_plate", exit
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -500,10 +545,10 @@ public class RagfairOfferGenerator(
|
||||
|
||||
var isBarterOffer = randomUtil.GetChance100(ragfairConfig.Dynamic.Barter.ChancePercent);
|
||||
var isPackOffer =
|
||||
randomUtil.GetChance100(ragfairConfig.Dynamic.Pack.ChancePercent) &&
|
||||
!isBarterOffer &&
|
||||
itemWithChildren.Count == 1 &&
|
||||
itemHelper.IsOfBaseclasses(
|
||||
randomUtil.GetChance100(ragfairConfig.Dynamic.Pack.ChancePercent)
|
||||
&& !isBarterOffer
|
||||
&& itemWithChildren.Count == 1
|
||||
&& itemHelper.IsOfBaseclasses(
|
||||
itemWithChildren[0].Template,
|
||||
ragfairConfig.Dynamic.Pack.ItemTypeWhitelist
|
||||
);
|
||||
@@ -513,15 +558,21 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
var armorConfig = ragfairConfig.Dynamic.Armor;
|
||||
|
||||
var shouldRemovePlates = randomUtil.GetChance100(armorConfig.RemoveRemovablePlateChance);
|
||||
if (shouldRemovePlates && itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template))
|
||||
var shouldRemovePlates = randomUtil.GetChance100(
|
||||
armorConfig.RemoveRemovablePlateChance
|
||||
);
|
||||
if (
|
||||
shouldRemovePlates
|
||||
&& itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template)
|
||||
)
|
||||
{
|
||||
var offerItemPlatesToRemove = itemWithChildren.Where(item =>
|
||||
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLower())
|
||||
);
|
||||
|
||||
// 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))
|
||||
var indexesToRemove = offerItemPlatesToRemove
|
||||
.Select(plateItem => itemWithChildren.IndexOf(plateItem))
|
||||
.ToHashSet();
|
||||
foreach (var index in indexesToRemove.OrderByDescending(x => x))
|
||||
{
|
||||
@@ -540,7 +591,11 @@ public class RagfairOfferGenerator(
|
||||
);
|
||||
|
||||
// Don't randomise pack items
|
||||
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer, desiredStackSize);
|
||||
barterScheme = CreateCurrencyBarterScheme(
|
||||
itemWithChildren,
|
||||
isPackOffer,
|
||||
desiredStackSize
|
||||
);
|
||||
}
|
||||
else if (isBarterOffer)
|
||||
{
|
||||
@@ -600,13 +655,14 @@ public class RagfairOfferGenerator(
|
||||
}
|
||||
|
||||
var blacklist = ragfairConfig.Dynamic.Blacklist;
|
||||
var childAssortItems = assortsClone.Items
|
||||
.Where(x => !string.Equals(x.ParentId, "hideout", StringComparison.Ordinal)).ToList();
|
||||
var childAssortItems = assortsClone
|
||||
.Items.Where(x => !string.Equals(x.ParentId, "hideout", StringComparison.Ordinal))
|
||||
.ToList();
|
||||
foreach (var item in assortsClone.Items)
|
||||
{
|
||||
// We only want to process 'base/root' items, no children
|
||||
if (item.SlotId != "hideout")
|
||||
// skip mod items
|
||||
// skip mod items
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -617,12 +673,17 @@ public class RagfairOfferGenerator(
|
||||
var itemDetails = itemHelper.GetItem(item.Template);
|
||||
if (!itemDetails.Key)
|
||||
{
|
||||
logger.Warning(localisationService.GetText("ragfair-tpl_not_a_valid_item", item.Template));
|
||||
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))
|
||||
if (
|
||||
blacklist.EnableBsgList
|
||||
&& !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -631,7 +692,7 @@ public class RagfairOfferGenerator(
|
||||
var isPreset = presetHelper.IsPreset(item.Id);
|
||||
var items = isPreset
|
||||
? ragfairServerHelper.GetPresetItems(item)
|
||||
: [item, ..itemHelper.FindAndReturnChildrenByAssort(item.Id, childAssortItems)];
|
||||
: [item, .. itemHelper.FindAndReturnChildrenByAssort(item.Id, childAssortItems)];
|
||||
|
||||
if (!assortsClone.BarterScheme.TryGetValue(item.Id, out var barterScheme))
|
||||
{
|
||||
@@ -642,7 +703,7 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
itemId = item.Id,
|
||||
tpl = item.Template,
|
||||
name = trader.Base.Nickname
|
||||
name = trader.Base.Nickname,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -652,7 +713,14 @@ public class RagfairOfferGenerator(
|
||||
var barterSchemeItems = barterScheme[0];
|
||||
var loyalLevel = assortsClone.LoyalLevelItems[item.Id];
|
||||
|
||||
CreateAndAddFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel, (int?) item.Upd.StackObjectsCount ?? 1);
|
||||
CreateAndAddFleaOffer(
|
||||
traderID,
|
||||
time,
|
||||
items,
|
||||
barterSchemeItems,
|
||||
loyalLevel,
|
||||
(int?)item.Upd.StackObjectsCount ?? 1
|
||||
);
|
||||
|
||||
// Refresh complete, reset flag to false
|
||||
trader.Base.RefreshTraderRagfairOffers = false;
|
||||
@@ -666,7 +734,11 @@ public class RagfairOfferGenerator(
|
||||
/// <param name="userID"> ID of owner of item </param>
|
||||
/// <param name="itemWithMods"> Item and mods, get condition of first item (only first array item is modified) </param>
|
||||
/// <param name="itemDetails"> DB details of first item</param>
|
||||
protected void RandomiseOfferItemUpdProperties(string userID, List<Item> itemWithMods, TemplateItem itemDetails)
|
||||
protected void RandomiseOfferItemUpdProperties(
|
||||
string userID,
|
||||
List<Item> itemWithMods,
|
||||
TemplateItem itemDetails
|
||||
)
|
||||
{
|
||||
// Add any missing properties to first item in array
|
||||
AddMissingConditions(itemWithMods[0]);
|
||||
@@ -675,13 +747,17 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
var parentId = GetDynamicConditionIdForTpl(itemDetails.Id);
|
||||
if (string.IsNullOrEmpty(parentId))
|
||||
// No condition details found, don't proceed with modifying item conditions
|
||||
// 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);
|
||||
}
|
||||
@@ -723,29 +799,35 @@ public class RagfairOfferGenerator(
|
||||
var rootItem = itemWithMods[0];
|
||||
|
||||
var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId];
|
||||
var maxMultiplier = randomUtil.GetDouble(itemConditionValues.Max.Min, itemConditionValues.Max.Min);
|
||||
var maxMultiplier = randomUtil.GetDouble(
|
||||
itemConditionValues.Max.Min,
|
||||
itemConditionValues.Max.Min
|
||||
);
|
||||
var currentMultiplier = randomUtil.GetDouble(
|
||||
itemConditionValues.Current.Min,
|
||||
itemConditionValues.Current.Max
|
||||
);
|
||||
|
||||
// Randomise armor + plates + armor related things
|
||||
if (itemHelper.ArmorItemCanHoldMods(rootItem.Template) ||
|
||||
itemHelper.IsOfBaseclasses(rootItem.Template, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT])
|
||||
)
|
||||
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");
|
||||
var visorMod = itemWithMods.FirstOrDefault(item =>
|
||||
item.ParentId == BaseClasses.ARMORED_EQUIPMENT && item.SlotId == "mod_equipment_000"
|
||||
);
|
||||
if (randomUtil.GetChance100(25) && visorMod != null)
|
||||
{
|
||||
itemHelper.AddUpdObjectToItem(visorMod);
|
||||
|
||||
visorMod.Upd.FaceShield = new UpdFaceShield
|
||||
{
|
||||
Hits = randomUtil.GetInt(1, 3)
|
||||
};
|
||||
visorMod.Upd.FaceShield = new UpdFaceShield { Hits = randomUtil.GetInt(1, 3) };
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -754,7 +836,12 @@ public class RagfairOfferGenerator(
|
||||
// Randomise Weapons
|
||||
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON))
|
||||
{
|
||||
RandomiseWeaponDurability(itemWithMods[0], itemDetails, maxMultiplier, currentMultiplier);
|
||||
RandomiseWeaponDurability(
|
||||
itemWithMods[0],
|
||||
itemDetails,
|
||||
maxMultiplier,
|
||||
currentMultiplier
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -762,7 +849,7 @@ public class RagfairOfferGenerator(
|
||||
if (rootItem.Upd?.MedKit != null)
|
||||
{
|
||||
// Randomize health
|
||||
var hpResource = Math.Round((double) rootItem.Upd.MedKit.HpResource * maxMultiplier);
|
||||
var hpResource = Math.Round((double)rootItem.Upd.MedKit.HpResource * maxMultiplier);
|
||||
rootItem.Upd.MedKit.HpResource = hpResource == 0D ? 1D : hpResource;
|
||||
return;
|
||||
}
|
||||
@@ -770,14 +857,15 @@ public class RagfairOfferGenerator(
|
||||
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));
|
||||
rootItem.Upd.Key.NumberOfUsages = (int?)
|
||||
Math.Round(itemDetails.Properties.MaximumNumberOfUsage.Value * (1 - maxMultiplier));
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.Upd?.FoodDrink != null)
|
||||
{
|
||||
// randomize food/drink value
|
||||
var hpPercent = Math.Round((double) itemDetails.Properties.MaxResource * maxMultiplier);
|
||||
var hpPercent = Math.Round((double)itemDetails.Properties.MaxResource * maxMultiplier);
|
||||
rootItem.Upd.FoodDrink.HpPercent = hpPercent == 0D ? 1D : hpPercent;
|
||||
|
||||
return;
|
||||
@@ -786,7 +874,9 @@ public class RagfairOfferGenerator(
|
||||
if (rootItem.Upd?.RepairKit != null)
|
||||
{
|
||||
// randomize repair kit (armor/weapon) uses
|
||||
var resource = Math.Round((double) itemDetails.Properties.MaxRepairResource * maxMultiplier);
|
||||
var resource = Math.Round(
|
||||
(double)itemDetails.Properties.MaxRepairResource * maxMultiplier
|
||||
);
|
||||
rootItem.Upd.RepairKit.Resource = resource == 0D ? 1D : resource;
|
||||
|
||||
return;
|
||||
@@ -795,11 +885,11 @@ public class RagfairOfferGenerator(
|
||||
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
|
||||
Value = remainingFuel,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -821,13 +911,19 @@ 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;
|
||||
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability));
|
||||
var lowestCurrentDurability =
|
||||
randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
|
||||
var chosenCurrentDurability = Math.Round(
|
||||
randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability)
|
||||
);
|
||||
|
||||
item.Upd.Repairable.Durability = chosenCurrentDurability == 0 ? 1D : chosenCurrentDurability; // Never var value become 0
|
||||
item.Upd.Repairable.Durability =
|
||||
chosenCurrentDurability == 0 ? 1D : chosenCurrentDurability; // Never var value become 0
|
||||
item.Upd.Repairable.MaxDurability = chosenMaxDurability;
|
||||
}
|
||||
|
||||
@@ -851,16 +947,22 @@ public class RagfairOfferGenerator(
|
||||
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 lowestMaxDurability =
|
||||
randomUtil.GetDouble(maxMultiplier, 1) * 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));
|
||||
var lowestCurrentDurability =
|
||||
randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
|
||||
var chosenCurrentDurability = Math.Round(
|
||||
randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability)
|
||||
);
|
||||
|
||||
armorItem.Upd.Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = chosenCurrentDurability == 0D ? 1D : chosenCurrentDurability, // Never var value become 0
|
||||
MaxDurability = chosenMaxDurability
|
||||
MaxDurability = chosenMaxDurability,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -886,7 +988,7 @@ public class RagfairOfferGenerator(
|
||||
item.Upd.Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = props.Durability,
|
||||
MaxDurability = props.Durability
|
||||
MaxDurability = props.Durability,
|
||||
};
|
||||
|
||||
return;
|
||||
@@ -894,20 +996,14 @@ public class RagfairOfferGenerator(
|
||||
|
||||
if (isMedkit && props.MaxHpResource > 0)
|
||||
{
|
||||
item.Upd.MedKit = new UpdMedKit
|
||||
{
|
||||
HpResource = props.MaxHpResource
|
||||
};
|
||||
item.Upd.MedKit = new UpdMedKit { HpResource = props.MaxHpResource };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKey)
|
||||
{
|
||||
item.Upd.Key = new UpdKey
|
||||
{
|
||||
NumberOfUsages = 0
|
||||
};
|
||||
item.Upd.Key = new UpdKey { NumberOfUsages = 0 };
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -915,20 +1011,14 @@ public class RagfairOfferGenerator(
|
||||
// Food/drink
|
||||
if (isConsumable)
|
||||
{
|
||||
item.Upd.FoodDrink = new UpdFoodDrink
|
||||
{
|
||||
HpPercent = props.MaxResource
|
||||
};
|
||||
item.Upd.FoodDrink = new UpdFoodDrink { HpPercent = props.MaxResource };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRepairKit)
|
||||
{
|
||||
item.Upd.RepairKit = new UpdRepairKit
|
||||
{
|
||||
Resource = props.MaxRepairResource
|
||||
};
|
||||
item.Upd.RepairKit = new UpdRepairKit { Resource = props.MaxRepairResource };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,7 +1028,10 @@ public class RagfairOfferGenerator(
|
||||
/// <param name="offerItems"> Items for sale in offer </param>
|
||||
/// <param name="barterConfig"> Barter config from ragfairConfig.Dynamic.barter </param>
|
||||
/// <returns> Barter scheme </returns>
|
||||
protected List<BarterScheme> CreateBarterBarterScheme(List<Item> offerItems, BarterDetails barterConfig)
|
||||
protected List<BarterScheme> CreateBarterBarterScheme(
|
||||
List<Item> offerItems,
|
||||
BarterDetails barterConfig
|
||||
)
|
||||
{
|
||||
// Get flea price of item being sold
|
||||
var priceOfOfferItem = ragfairPriceService.GetDynamicOfferPriceForOffer(
|
||||
@@ -954,13 +1047,17 @@ public class RagfairOfferGenerator(
|
||||
}
|
||||
|
||||
// Get a randomised number of barter items to list offer for
|
||||
var barterItemCount = randomUtil.GetInt(barterConfig.ItemCountMin, barterConfig.ItemCountMax);
|
||||
var barterItemCount = randomUtil.GetInt(
|
||||
barterConfig.ItemCountMin,
|
||||
barterConfig.ItemCountMax
|
||||
);
|
||||
|
||||
// Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k
|
||||
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();
|
||||
@@ -969,13 +1066,15 @@ public class RagfairOfferGenerator(
|
||||
var min = desiredItemCostRouble - offerCostVarianceRoubles;
|
||||
var max = desiredItemCostRouble + offerCostVarianceRoubles;
|
||||
var itemsInsidePriceBounds = itemFleaPrices.Where(itemAndPrice =>
|
||||
itemAndPrice.Price >= min &&
|
||||
itemAndPrice.Price <= max &&
|
||||
!string.Equals(itemAndPrice.Tpl, offerItems[0].Template,
|
||||
StringComparison.OrdinalIgnoreCase) // Don't allow the item being sold to be chosen
|
||||
itemAndPrice.Price >= min
|
||||
&& itemAndPrice.Price <= max
|
||||
&& !string.Equals(
|
||||
itemAndPrice.Tpl,
|
||||
offerItems[0].Template,
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
) // Don't allow the item being sold to be chosen
|
||||
);
|
||||
|
||||
|
||||
// No items on flea have a matching price, fall back to currency
|
||||
if (!itemsInsidePriceBounds.Any())
|
||||
{
|
||||
@@ -985,14 +1084,7 @@ public class RagfairOfferGenerator(
|
||||
// Choose random item from price-filtered flea items
|
||||
var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds.ToList());
|
||||
|
||||
return
|
||||
[
|
||||
new BarterScheme
|
||||
{
|
||||
Count = barterItemCount,
|
||||
Template = randomItem.Tpl
|
||||
}
|
||||
];
|
||||
return [new BarterScheme { Count = barterItemCount, Template = randomItem.Tpl }];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1008,16 +1100,13 @@ public class RagfairOfferGenerator(
|
||||
|
||||
// Only get prices for items that also exist in items.json
|
||||
var filteredFleaItems = fleaPrices
|
||||
.Select(kvTpl => new TplWithFleaPrice
|
||||
{
|
||||
Tpl = kvTpl.Key,
|
||||
Price = kvTpl.Value
|
||||
}
|
||||
)
|
||||
.Select(kvTpl => new TplWithFleaPrice { Tpl = kvTpl.Key, Price = kvTpl.Value })
|
||||
.Where(item => itemHelper.GetItem(item.Tpl).Key);
|
||||
|
||||
var itemTypeBlacklist = ragfairConfig.Dynamic.Barter.ItemTypeBlacklist;
|
||||
allowedFleaPriceItemsForBarter = filteredFleaItems.Where(item => !itemHelper.IsOfBaseclasses(item.Tpl, itemTypeBlacklist)).ToList();
|
||||
allowedFleaPriceItemsForBarter = filteredFleaItems
|
||||
.Where(item => !itemHelper.IsOfBaseclasses(item.Tpl, itemTypeBlacklist))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return allowedFleaPriceItemsForBarter;
|
||||
@@ -1037,15 +1126,13 @@ public class RagfairOfferGenerator(
|
||||
)
|
||||
{
|
||||
var currency = ragfairServerHelper.GetDynamicOfferCurrency();
|
||||
var price = ragfairPriceService.GetDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer) * multiplier;
|
||||
var price =
|
||||
ragfairPriceService.GetDynamicOfferPriceForOffer(
|
||||
offerWithChildren,
|
||||
currency,
|
||||
isPackOffer
|
||||
) * multiplier;
|
||||
|
||||
return
|
||||
[
|
||||
new BarterScheme
|
||||
{
|
||||
Count = price,
|
||||
Template = currency
|
||||
}
|
||||
];
|
||||
return [new BarterScheme { Count = price, Template = currency }];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,29 +37,10 @@ public class RepeatableQuestGenerator(
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, List<string>> _bodyPartsToClient = new()
|
||||
{
|
||||
{
|
||||
BodyParts.Arms, [
|
||||
BodyParts.LeftArm,
|
||||
BodyParts.RightArm
|
||||
]
|
||||
},
|
||||
{
|
||||
BodyParts.Legs, [
|
||||
BodyParts.LeftLeg,
|
||||
BodyParts.RightLeg
|
||||
]
|
||||
},
|
||||
{
|
||||
BodyParts.Head, [
|
||||
BodyParts.Head
|
||||
]
|
||||
},
|
||||
{
|
||||
BodyParts.Chest, [
|
||||
BodyParts.Chest,
|
||||
BodyParts.Stomach
|
||||
]
|
||||
},
|
||||
{ BodyParts.Arms, [BodyParts.LeftArm, BodyParts.RightArm] },
|
||||
{ BodyParts.Legs, [BodyParts.LeftLeg, BodyParts.RightLeg] },
|
||||
{ BodyParts.Head, [BodyParts.Head] },
|
||||
{ BodyParts.Chest, [BodyParts.Chest, BodyParts.Stomach] },
|
||||
};
|
||||
|
||||
protected int _maxRandomNumberAttempts = 6;
|
||||
@@ -88,8 +69,8 @@ public class RepeatableQuestGenerator(
|
||||
var questType = _randomUtil.DrawRandomFromList(questTypePool.Types).First();
|
||||
|
||||
// Get traders from whitelist and filter by quest type availability
|
||||
var traders = repeatableConfig.TraderWhitelist
|
||||
.Where(x => x.QuestTypes.Contains(questType))
|
||||
var traders = repeatableConfig
|
||||
.TraderWhitelist.Where(x => x.QuestTypes.Contains(questType))
|
||||
.Select(x => x.TraderId)
|
||||
.ToList();
|
||||
// filter out locked traders
|
||||
@@ -98,11 +79,34 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
return questType switch
|
||||
{
|
||||
"Elimination" => GenerateEliminationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig),
|
||||
"Completion" => GenerateCompletionQuest(sessionId, pmcLevel, traderId, repeatableConfig),
|
||||
"Exploration" => GenerateExplorationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig),
|
||||
"Pickup" => GeneratePickupQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig),
|
||||
_ => null
|
||||
"Elimination" => GenerateEliminationQuest(
|
||||
sessionId,
|
||||
pmcLevel,
|
||||
traderId,
|
||||
questTypePool,
|
||||
repeatableConfig
|
||||
),
|
||||
"Completion" => GenerateCompletionQuest(
|
||||
sessionId,
|
||||
pmcLevel,
|
||||
traderId,
|
||||
repeatableConfig
|
||||
),
|
||||
"Exploration" => GenerateExplorationQuest(
|
||||
sessionId,
|
||||
pmcLevel,
|
||||
traderId,
|
||||
questTypePool,
|
||||
repeatableConfig
|
||||
),
|
||||
"Pickup" => GeneratePickupQuest(
|
||||
sessionId,
|
||||
pmcLevel,
|
||||
traderId,
|
||||
questTypePool,
|
||||
repeatableConfig
|
||||
),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,13 +132,31 @@ public class RepeatableQuestGenerator(
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(pmcLevel, repeatableConfig);
|
||||
var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(
|
||||
pmcLevel,
|
||||
repeatableConfig
|
||||
);
|
||||
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 weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponRequirements);
|
||||
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 weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(
|
||||
_mathUtil,
|
||||
_cloner,
|
||||
eliminationConfig.WeaponRequirements
|
||||
);
|
||||
|
||||
// the difficulty of the quest varies in difficulty depending on the condition
|
||||
// possible conditions are
|
||||
@@ -157,8 +179,7 @@ public class RepeatableQuestGenerator(
|
||||
// times the number of kills we have to perform):
|
||||
|
||||
// The minimum difficulty is the difficulty for the most probable (= easiest target) with no additional conditions
|
||||
var minDifficulty =
|
||||
1 / targetsConfig.MaxProbability(); // min difficulty is the lowest amount of scavs without any constraints
|
||||
var minDifficulty = 1 / targetsConfig.MaxProbability(); // min difficulty is the lowest amount of scavs without any constraints
|
||||
|
||||
// Target on bodyPart max. difficulty is that of the least probable element
|
||||
var maxTargetDifficulty = 1 / targetsConfig.MinProbability();
|
||||
@@ -172,7 +193,10 @@ public class RepeatableQuestGenerator(
|
||||
var targetPool = questTypePool.Pool.Elimination;
|
||||
targetsConfig = targetsConfig.Filter(x => targetPool.Targets.ContainsKey(x.Key));
|
||||
|
||||
if (targetsConfig.Count == 0 || targetsConfig.All(x => x.Data.IsBoss.GetValueOrDefault(false)))
|
||||
if (
|
||||
targetsConfig.Count == 0
|
||||
|| targetsConfig.All(x => x.Data.IsBoss.GetValueOrDefault(false))
|
||||
)
|
||||
{
|
||||
// There are no more targets left for elimination; delete it as a possible quest type
|
||||
// also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination
|
||||
@@ -190,9 +214,13 @@ public class RepeatableQuestGenerator(
|
||||
// we use any as location if "any" is in the pool, and we don't hit the specific location random
|
||||
// we use any also if the random condition is not met in case only "any" was in the pool
|
||||
var locationKey = "any";
|
||||
if (locations.Contains("any") &&
|
||||
(eliminationConfig.SpecificLocationProbability < rand.NextDouble() || locations.Count <= 1)
|
||||
)
|
||||
if (
|
||||
locations.Contains("any")
|
||||
&& (
|
||||
eliminationConfig.SpecificLocationProbability < rand.NextDouble()
|
||||
|| locations.Count <= 1
|
||||
)
|
||||
)
|
||||
{
|
||||
locationKey = "any";
|
||||
targetPool.Targets.Remove(botTypeToEliminate);
|
||||
@@ -207,17 +235,21 @@ public class RepeatableQuestGenerator(
|
||||
locationKey = _randomUtil.DrawRandomFromList(locations).FirstOrDefault();
|
||||
|
||||
// Get a pool of locations the chosen bot type can be eliminated on
|
||||
if (!targetPool.Targets.TryGetValue(
|
||||
if (
|
||||
!targetPool.Targets.TryGetValue(
|
||||
botTypeToEliminate,
|
||||
out var possibleLocationPool
|
||||
))
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger.Warning($"Bot to kill: {botTypeToEliminate} not found in elimination dict");
|
||||
_logger.Warning(
|
||||
$"Bot to kill: {botTypeToEliminate} not found in elimination dict"
|
||||
);
|
||||
}
|
||||
|
||||
// Filter locations bot can be killed on to just those not chosen by key
|
||||
possibleLocationPool.Locations = possibleLocationPool.Locations
|
||||
.Where(location => location != locationKey)
|
||||
possibleLocationPool.Locations = possibleLocationPool
|
||||
.Locations.Where(location => location != locationKey)
|
||||
.ToList();
|
||||
|
||||
// None left after filtering
|
||||
@@ -231,7 +263,11 @@ public class RepeatableQuestGenerator(
|
||||
else
|
||||
{
|
||||
// Never should reach this if everything works out
|
||||
_logger.Error(_localisationService.GetText("quest-repeatable_elimination_generation_failed_please_report"));
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"quest-repeatable_elimination_generation_failed_please_report"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,49 +303,54 @@ public class RepeatableQuestGenerator(
|
||||
// Draw a distance condition
|
||||
int? distance = null;
|
||||
var distanceDifficulty = 0;
|
||||
var isDistanceRequirementAllowed = !eliminationConfig.DistLocationBlacklist.Contains(locationKey);
|
||||
var isDistanceRequirementAllowed = !eliminationConfig.DistLocationBlacklist.Contains(
|
||||
locationKey
|
||||
);
|
||||
|
||||
if (targetsConfig.Data(botTypeToEliminate).IsBoss.GetValueOrDefault(false))
|
||||
{
|
||||
// Get all boss spawn information
|
||||
var bossSpawns = _databaseService.GetLocations()
|
||||
var bossSpawns = _databaseService
|
||||
.GetLocations()
|
||||
.GetDictionary()
|
||||
.Select(x => x.Value)
|
||||
.Where(x => x.Base?.Id != null)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Base.Id,
|
||||
BossSpawn = x.Base.BossLocationSpawn
|
||||
}
|
||||
);
|
||||
.Select(x => new { x.Base.Id, BossSpawn = x.Base.BossLocationSpawn });
|
||||
// filter for the current boss to spawn on map
|
||||
var thisBossSpawns = bossSpawns
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
BossSpawn = x.BossSpawn
|
||||
.Where(e => e.BossName == botTypeToEliminate)
|
||||
}
|
||||
)
|
||||
{
|
||||
x.Id,
|
||||
BossSpawn = x.BossSpawn.Where(e => e.BossName == botTypeToEliminate),
|
||||
})
|
||||
.Where(x => x.BossSpawn.Count() > 0);
|
||||
// remove blacklisted locations
|
||||
var allowedSpawns = thisBossSpawns.Where(x => !eliminationConfig.DistLocationBlacklist.Contains(x.Id));
|
||||
var allowedSpawns = thisBossSpawns.Where(x =>
|
||||
!eliminationConfig.DistLocationBlacklist.Contains(x.Id)
|
||||
);
|
||||
// if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement
|
||||
isDistanceRequirementAllowed = isDistanceRequirementAllowed && allowedSpawns.Count() > 0;
|
||||
isDistanceRequirementAllowed =
|
||||
isDistanceRequirementAllowed && allowedSpawns.Count() > 0;
|
||||
}
|
||||
|
||||
if (eliminationConfig.DistanceProbability > rand.NextDouble() && isDistanceRequirementAllowed)
|
||||
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);
|
||||
distance = (int)Math.Ceiling((decimal)(distance / 5)) * 5;
|
||||
distanceDifficulty = (int)(
|
||||
maxDistDifficulty * distance / eliminationConfig.MaxDistance
|
||||
);
|
||||
}
|
||||
|
||||
string? allowedWeaponsCategory = null;
|
||||
@@ -321,18 +362,18 @@ public class RepeatableQuestGenerator(
|
||||
List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"];
|
||||
|
||||
// Filter out close range weapons from long distance requirement
|
||||
weaponCategoryRequirementConfig
|
||||
.RemoveAll(category => weaponTypeBlacklist
|
||||
.Contains(category.Key));
|
||||
weaponCategoryRequirementConfig.RemoveAll(category =>
|
||||
weaponTypeBlacklist.Contains(category.Key)
|
||||
);
|
||||
}
|
||||
else if (distance < 20)
|
||||
{
|
||||
List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"];
|
||||
|
||||
// Filter out far range weapons from close distance requirement
|
||||
weaponCategoryRequirementConfig
|
||||
.RemoveAll(category => weaponTypeBlacklist
|
||||
.Contains(category.Key));
|
||||
weaponCategoryRequirementConfig.RemoveAll(category =>
|
||||
weaponTypeBlacklist.Contains(category.Key)
|
||||
);
|
||||
}
|
||||
|
||||
// Pick a weighted weapon category
|
||||
@@ -344,16 +385,25 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
// Only allow a specific weapon requirement if a weapon category was not chosen
|
||||
string? allowedWeapon = null;
|
||||
if (allowedWeaponsCategory is not null && eliminationConfig.WeaponRequirementProbability > rand.NextDouble())
|
||||
if (
|
||||
allowedWeaponsCategory is not null
|
||||
&& eliminationConfig.WeaponRequirementProbability > rand.NextDouble()
|
||||
)
|
||||
{
|
||||
var weaponRequirement = weaponRequirementConfig.Draw(1, false);
|
||||
var specificAllowedWeaponCategory = weaponRequirementConfig.Data(weaponRequirement[0]);
|
||||
var allowedWeapons = _itemHelper.GetItemTplsOfBaseType(specificAllowedWeaponCategory[0]);
|
||||
var allowedWeapons = _itemHelper.GetItemTplsOfBaseType(
|
||||
specificAllowedWeaponCategory[0]
|
||||
);
|
||||
allowedWeapon = _randomUtil.GetArrayValue(allowedWeapons);
|
||||
}
|
||||
|
||||
// Draw how many npm kills are required
|
||||
var desiredKillCount = GetEliminationKillCount(botTypeToEliminate, targetsConfig, eliminationConfig);
|
||||
var desiredKillCount = GetEliminationKillCount(
|
||||
botTypeToEliminate,
|
||||
targetsConfig,
|
||||
eliminationConfig
|
||||
);
|
||||
var killDifficulty = desiredKillCount;
|
||||
|
||||
// not perfectly happy here; we give difficulty = 1 to the quest reward generation when we have the most difficult mission
|
||||
@@ -372,7 +422,12 @@ public class RepeatableQuestGenerator(
|
||||
// crazy maximum difficulty will lead to a higher difficulty reward gain factor than 1
|
||||
var difficulty = _mathUtil.MapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2);
|
||||
|
||||
var quest = GenerateRepeatableTemplate("Elimination", traderId, repeatableConfig.Side, sessionId);
|
||||
var quest = GenerateRepeatableTemplate(
|
||||
"Elimination",
|
||||
traderId,
|
||||
repeatableConfig.Side,
|
||||
sessionId
|
||||
);
|
||||
|
||||
// ASSUMPTION: All fence quests are for scavs
|
||||
if (traderId == Traders.FENCE)
|
||||
@@ -427,19 +482,29 @@ public class RepeatableQuestGenerator(
|
||||
protected int GetEliminationKillCount(
|
||||
string targetKey,
|
||||
ProbabilityObjectArray<string, BossInfo> targetsConfig,
|
||||
EliminationConfig eliminationConfig)
|
||||
EliminationConfig eliminationConfig
|
||||
)
|
||||
{
|
||||
if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false))
|
||||
{
|
||||
return _randomUtil.RandInt(eliminationConfig.MinBossKills.Value, eliminationConfig.MaxBossKills + 1);
|
||||
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.MinPmcKills.Value,
|
||||
eliminationConfig.MaxPmcKills + 1
|
||||
);
|
||||
}
|
||||
|
||||
return _randomUtil.RandInt(eliminationConfig.MinKills.Value, eliminationConfig.MaxKills + 1);
|
||||
return _randomUtil.RandInt(
|
||||
eliminationConfig.MinKills.Value,
|
||||
eliminationConfig.MaxKills + 1
|
||||
);
|
||||
}
|
||||
|
||||
protected double DifficultyWeighing(
|
||||
@@ -447,7 +512,8 @@ public class RepeatableQuestGenerator(
|
||||
double bodyPart,
|
||||
int dist,
|
||||
int kill,
|
||||
int weaponRequirement)
|
||||
int weaponRequirement
|
||||
)
|
||||
{
|
||||
return Math.Sqrt(Math.Sqrt(target) + bodyPart + dist + weaponRequirement) * kill;
|
||||
}
|
||||
@@ -466,7 +532,7 @@ public class RepeatableQuestGenerator(
|
||||
Id = _hashUtil.Generate(),
|
||||
DynamicLocale = true,
|
||||
Target = new ListOrT<string>(location, null),
|
||||
ConditionType = "Location"
|
||||
ConditionType = "Location",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -495,12 +561,8 @@ public class RepeatableQuestGenerator(
|
||||
Value = 1,
|
||||
ResetOnSessionEnd = false,
|
||||
EnemyHealthEffects = [],
|
||||
Daytime = new DaytimeCounter
|
||||
{
|
||||
From = 0,
|
||||
To = 0
|
||||
},
|
||||
ConditionType = "Kills"
|
||||
Daytime = new DaytimeCounter { From = 0, To = 0 },
|
||||
ConditionType = "Kills",
|
||||
};
|
||||
|
||||
if (target.StartsWith("boss"))
|
||||
@@ -521,7 +583,7 @@ public class RepeatableQuestGenerator(
|
||||
killConditionProps.Distance = new CounterConditionDistance
|
||||
{
|
||||
CompareMethod = ">=",
|
||||
Value = distance.Value
|
||||
Value = distance.Value,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -567,39 +629,49 @@ public class RepeatableQuestGenerator(
|
||||
var levelsConfig = repeatableConfig.RewardScaling.Levels;
|
||||
var roublesConfig = repeatableConfig.RewardScaling.Roubles;
|
||||
|
||||
var quest = GenerateRepeatableTemplate("Completion", traderId, repeatableConfig.Side, sessionId);
|
||||
var quest = GenerateRepeatableTemplate(
|
||||
"Completion",
|
||||
traderId,
|
||||
repeatableConfig.Side,
|
||||
sessionId
|
||||
);
|
||||
|
||||
// Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existent"
|
||||
var itemsToRetrievePool = GetItemsToRetrievePool(completionConfig, repeatableConfig.RewardBlacklist);
|
||||
var itemsToRetrievePool = GetItemsToRetrievePool(
|
||||
completionConfig,
|
||||
repeatableConfig.RewardBlacklist
|
||||
);
|
||||
|
||||
// Be fair, don't value the items be more expensive than the reward
|
||||
var multiplier = _randomUtil.GetDouble(0.5, 1);
|
||||
var roublesBudget = Math.Floor(
|
||||
(double) (_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier)
|
||||
(double)(_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier)
|
||||
);
|
||||
roublesBudget = Math.Max(roublesBudget, 5000d);
|
||||
var itemSelection = itemsToRetrievePool.Where(itemTpl => _itemHelper.GetItemPrice(itemTpl) < roublesBudget
|
||||
)
|
||||
var itemSelection = itemsToRetrievePool
|
||||
.Where(itemTpl => _itemHelper.GetItemPrice(itemTpl) < roublesBudget)
|
||||
.ToList();
|
||||
|
||||
// We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as
|
||||
// [{"minPlayerLevel": 1, "itemIds": ["id1",...]}, {"minPlayerLevel": 15, "itemIds": ["id3",...]}]
|
||||
if (repeatableConfig.QuestConfig.Completion.UseWhitelist.GetValueOrDefault(false))
|
||||
{
|
||||
var itemWhitelist = _databaseService.GetTemplates().RepeatableQuests.Data.Completion.ItemsWhitelist;
|
||||
var itemWhitelist = _databaseService
|
||||
.GetTemplates()
|
||||
.RepeatableQuests.Data.Completion.ItemsWhitelist;
|
||||
|
||||
// Filter and concatenate items according to current player level
|
||||
var itemIdsWhitelisted = itemWhitelist
|
||||
.Where(p => p.MinPlayerLevel <= pmcLevel)
|
||||
.SelectMany(x => x.ItemIds)
|
||||
.ToHashSet(); //.Aggregate((a, p) => a.Concat(p.ItemIds), []);
|
||||
itemSelection = itemSelection.Where(x =>
|
||||
{
|
||||
// Whitelist can contain item tpls and item base type ids
|
||||
return itemIdsWhitelisted.Any(v => _itemHelper.IsOfBaseclass(x, v)) ||
|
||||
itemIdsWhitelisted.Contains(x);
|
||||
}
|
||||
)
|
||||
itemSelection = itemSelection
|
||||
.Where(x =>
|
||||
{
|
||||
// Whitelist can contain item tpls and item base type ids
|
||||
return itemIdsWhitelisted.Any(v => _itemHelper.IsOfBaseclass(x, v))
|
||||
|| itemIdsWhitelisted.Contains(x);
|
||||
})
|
||||
.ToList();
|
||||
// check if items are missing
|
||||
// var flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []);
|
||||
@@ -608,7 +680,9 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
if (repeatableConfig.QuestConfig.Completion.UseBlacklist.GetValueOrDefault(false))
|
||||
{
|
||||
var itemBlacklist = _databaseService.GetTemplates().RepeatableQuests.Data.Completion.ItemsBlacklist;
|
||||
var itemBlacklist = _databaseService
|
||||
.GetTemplates()
|
||||
.RepeatableQuests.Data.Completion.ItemsBlacklist;
|
||||
|
||||
// Filter and concatenate the arrays according to current player level
|
||||
var itemIdsBlacklisted = itemBlacklist
|
||||
@@ -616,12 +690,12 @@ public class RepeatableQuestGenerator(
|
||||
.SelectMany(x => x.ItemIds)
|
||||
.ToHashSet(); //.Aggregate(List<ItemsBlacklist> , (a, p) => a.Concat(p.ItemIds) );
|
||||
|
||||
itemSelection = itemSelection.Where(x =>
|
||||
{
|
||||
return itemIdsBlacklisted.All(v => !_itemHelper.IsOfBaseclass(x, v)) ||
|
||||
!itemIdsBlacklisted.Contains(x);
|
||||
}
|
||||
)
|
||||
itemSelection = itemSelection
|
||||
.Where(x =>
|
||||
{
|
||||
return itemIdsBlacklisted.All(v => !_itemHelper.IsOfBaseclass(x, v))
|
||||
|| !itemIdsBlacklisted.Contains(x);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -638,7 +712,10 @@ public class RepeatableQuestGenerator(
|
||||
}
|
||||
|
||||
// Store the indexes of items we are asking player to supply
|
||||
var distinctItemsToRetrieveCount = _randomUtil.GetInt(1, completionConfig.UniqueItemCount.Value);
|
||||
var distinctItemsToRetrieveCount = _randomUtil.GetInt(
|
||||
1,
|
||||
completionConfig.UniqueItemCount.Value
|
||||
);
|
||||
var chosenRequirementItemsTpls = new List<string>();
|
||||
var usedItemIndexes = new HashSet<int>();
|
||||
for (var i = 0; i < distinctItemsToRetrieveCount; i++)
|
||||
@@ -664,11 +741,7 @@ public class RepeatableQuestGenerator(
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"repeatable-no_reward_item_found_in_price_range",
|
||||
new
|
||||
{
|
||||
minPrice = 0,
|
||||
roublesBudget
|
||||
}
|
||||
new { minPrice = 0, roublesBudget }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -686,11 +759,11 @@ public class RepeatableQuestGenerator(
|
||||
var value = minValue;
|
||||
|
||||
// Get the value range within budget
|
||||
var x = (int) Math.Floor(roublesBudget / itemPrice);
|
||||
var x = (int)Math.Floor(roublesBudget / itemPrice);
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
@@ -699,13 +772,20 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
// Push a CompletionCondition with the item and the amount of the item into quest
|
||||
chosenRequirementItemsTpls.Add(tplChosen);
|
||||
quest.Conditions.AvailableForFinish.Add(GenerateCompletionAvailableForFinish(tplChosen, value, repeatableConfig.QuestConfig.Completion));
|
||||
quest.Conditions.AvailableForFinish.Add(
|
||||
GenerateCompletionAvailableForFinish(
|
||||
tplChosen,
|
||||
value,
|
||||
repeatableConfig.QuestConfig.Completion
|
||||
)
|
||||
);
|
||||
|
||||
// Is there budget left for more items
|
||||
if (roublesBudget > 0)
|
||||
{
|
||||
// Reduce item pool to fit budget
|
||||
itemSelection = itemSelection.Where(tpl => _itemHelper.GetItemPrice(tpl) < roublesBudget)
|
||||
itemSelection = itemSelection
|
||||
.Where(tpl => _itemHelper.GetItemPrice(tpl) < roublesBudget)
|
||||
.ToList();
|
||||
if (!itemSelection.Any())
|
||||
{
|
||||
@@ -737,35 +817,39 @@ public class RepeatableQuestGenerator(
|
||||
/// <param name="completionConfig">Completion quest type config</param>
|
||||
/// <param name="itemTplBlacklist">Item tpls to not add to pool</param>
|
||||
/// <returns>Set of item tpls</returns>
|
||||
protected HashSet<string> GetItemsToRetrievePool(Completion completionConfig, HashSet<string> itemTplBlacklist)
|
||||
protected HashSet<string> GetItemsToRetrievePool(
|
||||
Completion completionConfig,
|
||||
HashSet<string> itemTplBlacklist
|
||||
)
|
||||
{
|
||||
// Get seasonal items that should not be added to pool as seasonal event is not active
|
||||
var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
|
||||
// Check for specific base classes which don't make sense as reward item
|
||||
// also check if the price is greater than 0; there are some items whose price can not be found
|
||||
return _databaseService.GetItems()
|
||||
return _databaseService
|
||||
.GetItems()
|
||||
.Values.Where(itemTemplate =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (itemTemplate.Parent == string.Empty)
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (itemTemplate.Parent == string.Empty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seasonalItems.Contains(itemTemplate.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valid reward items share same logic as items to retrieve
|
||||
return _repeatableQuestRewardGenerator.IsValidRewardItem(
|
||||
itemTemplate.Id,
|
||||
itemTplBlacklist,
|
||||
completionConfig.RequiredItemTypeBlacklist
|
||||
);
|
||||
return false;
|
||||
}
|
||||
).Select(item => item.Id)
|
||||
|
||||
if (seasonalItems.Contains(itemTemplate.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valid reward items share same logic as items to retrieve
|
||||
return _repeatableQuestRewardGenerator.IsValidRewardItem(
|
||||
itemTemplate.Id,
|
||||
itemTplBlacklist,
|
||||
completionConfig.RequiredItemTypeBlacklist
|
||||
);
|
||||
})
|
||||
.Select(item => item.Id)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
@@ -779,13 +863,23 @@ public class RepeatableQuestGenerator(
|
||||
/// <param name="value">Amount of items of this specific type to request</param>
|
||||
/// <param name="completionConfig">Completion config from quest.json</param>
|
||||
/// <returns>object of "Completion"-condition</returns>
|
||||
protected QuestCondition GenerateCompletionAvailableForFinish(string itemTpl,
|
||||
protected QuestCondition GenerateCompletionAvailableForFinish(
|
||||
string itemTpl,
|
||||
double value,
|
||||
Completion completionConfig)
|
||||
Completion completionConfig
|
||||
)
|
||||
{
|
||||
var onlyFoundInRaid = completionConfig.RequiredItemsAreFiR;
|
||||
var minDurability = _itemHelper.IsOfBaseclasses(itemTpl, [BaseClasses.WEAPON, BaseClasses.ARMOR])
|
||||
? _randomUtil.GetArrayValue([completionConfig.RequiredItemMinDurabilityMinMax.Min, completionConfig.RequiredItemMinDurabilityMinMax.Max])
|
||||
var minDurability = _itemHelper.IsOfBaseclasses(
|
||||
itemTpl,
|
||||
[BaseClasses.WEAPON, BaseClasses.ARMOR]
|
||||
)
|
||||
? _randomUtil.GetArrayValue(
|
||||
[
|
||||
completionConfig.RequiredItemMinDurabilityMinMax.Min,
|
||||
completionConfig.RequiredItemMinDurabilityMinMax.Max,
|
||||
]
|
||||
)
|
||||
: 0;
|
||||
|
||||
// Dog tags MUST NOT be FiR for them to work
|
||||
@@ -809,7 +903,7 @@ public class RepeatableQuestGenerator(
|
||||
DogtagLevel = 0,
|
||||
OnlyFoundInRaid = onlyFoundInRaid,
|
||||
IsEncoded = false,
|
||||
ConditionType = "HandoverItem"
|
||||
ConditionType = "HandoverItem",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -830,11 +924,13 @@ public class RepeatableQuestGenerator(
|
||||
int pmcLevel,
|
||||
string traderId,
|
||||
QuestTypePool questTypePool,
|
||||
RepeatableQuestConfig repeatableConfig)
|
||||
RepeatableQuestConfig repeatableConfig
|
||||
)
|
||||
{
|
||||
var explorationConfig = repeatableConfig.QuestConfig.Exploration;
|
||||
var requiresSpecificExtract =
|
||||
_randomUtil.Random.Next() < repeatableConfig.QuestConfig.Exploration.SpecificExits.Probability;
|
||||
_randomUtil.Random.Next()
|
||||
< repeatableConfig.QuestConfig.Exploration.SpecificExits.Probability;
|
||||
|
||||
if (questTypePool.Pool.Exploration.Locations.Count == 0)
|
||||
{
|
||||
@@ -845,7 +941,9 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
// If location drawn is factory, it's possible to either get factory4_day and factory4_night or only one
|
||||
// of the both
|
||||
var locationKey = _randomUtil.DrawRandomFromDict(questTypePool.Pool.Exploration.Locations)[0];
|
||||
var locationKey = _randomUtil.DrawRandomFromDict(questTypePool.Pool.Exploration.Locations)[
|
||||
0
|
||||
];
|
||||
var locationTarget = questTypePool.Pool.Exploration.Locations[locationKey];
|
||||
|
||||
// Remove the location from the available pool
|
||||
@@ -857,25 +955,34 @@ public class RepeatableQuestGenerator(
|
||||
: explorationConfig.MaximumExtracts + 1;
|
||||
var numExtracts = _randomUtil.RandInt(1, exitTimesMax);
|
||||
|
||||
var quest = GenerateRepeatableTemplate("Exploration", traderId, repeatableConfig.Side, sessionId);
|
||||
var quest = GenerateRepeatableTemplate(
|
||||
"Exploration",
|
||||
traderId,
|
||||
repeatableConfig.Side,
|
||||
sessionId
|
||||
);
|
||||
|
||||
var exitStatusCondition = new QuestConditionCounterCondition
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
DynamicLocale = true,
|
||||
Status = ["Survived"],
|
||||
ConditionType = "ExitStatus"
|
||||
ConditionType = "ExitStatus",
|
||||
};
|
||||
var locationCondition = new QuestConditionCounterCondition
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
DynamicLocale = true,
|
||||
Target = new ListOrT<string>(locationTarget, null),
|
||||
ConditionType = "Location"
|
||||
ConditionType = "Location",
|
||||
};
|
||||
|
||||
quest.Conditions.AvailableForFinish[0].Counter.Id = _hashUtil.Generate();
|
||||
quest.Conditions.AvailableForFinish[0].Counter.Conditions = [exitStatusCondition, locationCondition];
|
||||
quest.Conditions.AvailableForFinish[0].Counter.Conditions =
|
||||
[
|
||||
exitStatusCondition,
|
||||
locationCondition,
|
||||
];
|
||||
quest.Conditions.AvailableForFinish[0].Value = numExtracts;
|
||||
quest.Conditions.AvailableForFinish[0].Id = _hashUtil.Generate();
|
||||
quest.Location = GetQuestLocationByMapId(locationKey.ToString());
|
||||
@@ -889,9 +996,10 @@ public class RepeatableQuestGenerator(
|
||||
var exitPool = mapExits.Where(exit => exit.Chance > 0).ToList();
|
||||
|
||||
// Exclude exits with a requirement to leave (e.g. car extracts)
|
||||
var possibleExits = exitPool.Where(exit =>
|
||||
exit.PassageRequirement is not null ||
|
||||
repeatableConfig.QuestConfig.Exploration.SpecificExits.PassageRequirementWhitelist.Contains(
|
||||
var possibleExits = exitPool
|
||||
.Where(exit =>
|
||||
exit.PassageRequirement is not null
|
||||
|| repeatableConfig.QuestConfig.Exploration.SpecificExits.PassageRequirementWhitelist.Contains(
|
||||
"PassageRequirement"
|
||||
)
|
||||
)
|
||||
@@ -899,7 +1007,9 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
if (possibleExits.Count == 0)
|
||||
{
|
||||
_logger.Error($"Unable to choose specific exit on map: {locationKey}, Possible exit pool was empty");
|
||||
_logger.Error(
|
||||
$"Unable to choose specific exit on map: {locationKey}, Possible exit pool was empty"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -914,7 +1024,13 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
// Difficulty for exploration goes from 1 extract to maxExtracts
|
||||
// Difficulty for reward goes from 0.2...1 -> map
|
||||
var difficulty = _mathUtil.MapToRange(numExtracts, 1, explorationConfig.MaximumExtracts.Value, 0.2, 1);
|
||||
var difficulty = _mathUtil.MapToRange(
|
||||
numExtracts,
|
||||
1,
|
||||
explorationConfig.MaximumExtracts.Value,
|
||||
0.2,
|
||||
1
|
||||
);
|
||||
quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward(
|
||||
pmcLevel,
|
||||
difficulty,
|
||||
@@ -944,13 +1060,21 @@ public class RepeatableQuestGenerator(
|
||||
int pmcLevel,
|
||||
string traderId,
|
||||
QuestTypePool questTypePool,
|
||||
RepeatableQuestConfig repeatableConfig)
|
||||
RepeatableQuestConfig repeatableConfig
|
||||
)
|
||||
{
|
||||
var pickupConfig = repeatableConfig.QuestConfig.Pickup;
|
||||
|
||||
var quest = GenerateRepeatableTemplate("Pickup", traderId, repeatableConfig.Side, sessionId);
|
||||
var quest = GenerateRepeatableTemplate(
|
||||
"Pickup",
|
||||
traderId,
|
||||
repeatableConfig.Side,
|
||||
sessionId
|
||||
);
|
||||
|
||||
var itemTypeToFetchWithCount = _randomUtil.GetArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount);
|
||||
var itemTypeToFetchWithCount = _randomUtil.GetArrayValue(
|
||||
pickupConfig.ItemTypeToFetchWithMaxCount
|
||||
);
|
||||
var itemCountToFetch = _randomUtil.RandInt(
|
||||
itemTypeToFetchWithCount.MinimumPickupCount.Value,
|
||||
itemTypeToFetchWithCount.MaximumPickupCount + 1
|
||||
@@ -959,18 +1083,25 @@ public class RepeatableQuestGenerator(
|
||||
// var locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0];
|
||||
// var locationTarget = questTypePool.pool.Pickup.locations[locationKey];
|
||||
|
||||
var findCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x => x.ConditionType == "FindItem");
|
||||
var findCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x =>
|
||||
x.ConditionType == "FindItem"
|
||||
);
|
||||
findCondition.Target = new ListOrT<string>([itemTypeToFetchWithCount.ItemType], null);
|
||||
findCondition.Value = itemCountToFetch;
|
||||
|
||||
var counterCreatorCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x => x.ConditionType == "CounterCreator"
|
||||
var counterCreatorCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x =>
|
||||
x.ConditionType == "CounterCreator"
|
||||
);
|
||||
// var locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location");
|
||||
// (locationCondition._props as ILocationConditionProps).target = [...locationTarget];
|
||||
|
||||
var equipmentCondition = counterCreatorCondition.Counter.Conditions.FirstOrDefault(x => x.ConditionType == "Equipment"
|
||||
var equipmentCondition = counterCreatorCondition.Counter.Conditions.FirstOrDefault(x =>
|
||||
x.ConditionType == "Equipment"
|
||||
);
|
||||
equipmentCondition.EquipmentInclusive = [[itemTypeToFetchWithCount.ItemType]];
|
||||
equipmentCondition.EquipmentInclusive =
|
||||
[
|
||||
[itemTypeToFetchWithCount.ItemType],
|
||||
];
|
||||
|
||||
// Add rewards
|
||||
quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward(
|
||||
@@ -1007,7 +1138,7 @@ public class RepeatableQuestGenerator(
|
||||
Id = _hashUtil.Generate(),
|
||||
DynamicLocale = true,
|
||||
ExitName = exit.Name,
|
||||
ConditionType = "ExitName"
|
||||
ConditionType = "ExitName",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1027,7 +1158,8 @@ public class RepeatableQuestGenerator(
|
||||
string type,
|
||||
string traderId,
|
||||
string side,
|
||||
string sessionId)
|
||||
string sessionId
|
||||
)
|
||||
{
|
||||
RepeatableQuest questData = null;
|
||||
switch (type)
|
||||
@@ -1082,35 +1214,35 @@ public class RepeatableQuestGenerator(
|
||||
// Force REF templates to use prapors ID - solves missing text issue
|
||||
var desiredTraderId = traderId == Traders.REF ? Traders.PRAPOR : traderId;
|
||||
|
||||
questClone.Name = questClone.Name
|
||||
.Replace("{traderId}", traderId)
|
||||
questClone.Name = questClone
|
||||
.Name.Replace("{traderId}", traderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.Note = questClone.Note
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.Note = questClone
|
||||
.Note.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.Description = questClone.Description
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.Description = questClone
|
||||
.Description.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.SuccessMessageText = questClone.SuccessMessageText
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.SuccessMessageText = questClone
|
||||
.SuccessMessageText.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.FailMessageText = questClone.FailMessageText
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.FailMessageText = questClone
|
||||
.FailMessageText.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.StartedMessageText = questClone.StartedMessageText
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.StartedMessageText = questClone
|
||||
.StartedMessageText.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.ChangeQuestMessageText = questClone.ChangeQuestMessageText
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.ChangeQuestMessageText = questClone
|
||||
.ChangeQuestMessageText.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.AcceptPlayerMessage = questClone.AcceptPlayerMessage
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.AcceptPlayerMessage = questClone
|
||||
.AcceptPlayerMessage.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.DeclinePlayerMessage = questClone.DeclinePlayerMessage
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.DeclinePlayerMessage = questClone
|
||||
.DeclinePlayerMessage.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
questClone.CompletePlayerMessage = questClone.CompletePlayerMessage
|
||||
.Replace("{traderId}", desiredTraderId)
|
||||
questClone.CompletePlayerMessage = questClone
|
||||
.CompletePlayerMessage.Replace("{traderId}", desiredTraderId)
|
||||
.Replace("{templateId}", questClone.TemplateId);
|
||||
|
||||
questClone.QuestStatus.Id = _hashUtil.Generate();
|
||||
|
||||
@@ -64,10 +64,15 @@ public class RepeatableQuestRewardGenerator(
|
||||
string traderId,
|
||||
RepeatableQuestConfig repeatableConfig,
|
||||
BaseQuestConfig eliminationConfig,
|
||||
List<string>? rewardTplBlacklist = null)
|
||||
List<string>? rewardTplBlacklist = null
|
||||
)
|
||||
{
|
||||
// Get vars to configure rewards with
|
||||
var rewardParams = GetQuestRewardValues(repeatableConfig.RewardScaling, difficulty, pmcLevel);
|
||||
var rewardParams = GetQuestRewardValues(
|
||||
repeatableConfig.RewardScaling,
|
||||
difficulty,
|
||||
pmcLevel
|
||||
);
|
||||
|
||||
// Get budget to spend on item rewards (copy of raw roubles given)
|
||||
var itemRewardBudget = rewardParams.RewardRoubles;
|
||||
@@ -77,7 +82,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
{
|
||||
Started = [],
|
||||
Success = [],
|
||||
Fail = []
|
||||
Fail = [],
|
||||
};
|
||||
|
||||
// Start reward index to keep track
|
||||
@@ -95,28 +100,40 @@ public class RepeatableQuestRewardGenerator(
|
||||
AvailableInGameEditions = [],
|
||||
Index = rewardIndex,
|
||||
Value = rewardParams.RewardXP,
|
||||
Type = RewardType.Experience
|
||||
Type = RewardType.Experience,
|
||||
}
|
||||
);
|
||||
rewardIndex++;
|
||||
}
|
||||
|
||||
// Add money reward
|
||||
rewards.Success.Add(GetMoneyReward(traderId, rewardParams.RewardRoubles.Value, rewardIndex));
|
||||
rewards.Success.Add(
|
||||
GetMoneyReward(traderId, rewardParams.RewardRoubles.Value, rewardIndex)
|
||||
);
|
||||
rewardIndex++;
|
||||
|
||||
// Add GP coin reward
|
||||
rewards.Success.Add(GenerateItemReward(Money.GP, rewardParams.GpCoinRewardCount.Value, rewardIndex));
|
||||
rewards.Success.Add(
|
||||
GenerateItemReward(Money.GP, rewardParams.GpCoinRewardCount.Value, rewardIndex)
|
||||
);
|
||||
rewardIndex++;
|
||||
|
||||
// Add preset weapon to reward if checks pass
|
||||
var traderWhitelistDetails = repeatableConfig.TraderWhitelist.FirstOrDefault(traderWhitelist => traderWhitelist.TraderId == traderId
|
||||
var traderWhitelistDetails = repeatableConfig.TraderWhitelist.FirstOrDefault(
|
||||
traderWhitelist => traderWhitelist.TraderId == traderId
|
||||
);
|
||||
if (traderWhitelistDetails?.RewardCanBeWeapon ??
|
||||
(false && _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent ?? 0))
|
||||
)
|
||||
if (
|
||||
traderWhitelistDetails?.RewardCanBeWeapon
|
||||
?? (
|
||||
false
|
||||
&& _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent ?? 0)
|
||||
)
|
||||
)
|
||||
{
|
||||
var chosenWeapon = GetRandomWeaponPresetWithinBudget(itemRewardBudget.Value, rewardIndex);
|
||||
var chosenWeapon = GetRandomWeaponPresetWithinBudget(
|
||||
itemRewardBudget.Value,
|
||||
rewardIndex
|
||||
);
|
||||
if (chosenWeapon is not null)
|
||||
{
|
||||
rewards.Success.Add(chosenWeapon.Value.Key);
|
||||
@@ -127,11 +144,16 @@ public class RepeatableQuestRewardGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
var inBudgetRewardItemPool = ChooseRewardItemsWithinBudget(repeatableConfig, itemRewardBudget, traderId);
|
||||
var inBudgetRewardItemPool = ChooseRewardItemsWithinBudget(
|
||||
repeatableConfig,
|
||||
itemRewardBudget,
|
||||
traderId
|
||||
);
|
||||
if (rewardTplBlacklist is not null)
|
||||
{
|
||||
// Filter reward pool of items from blacklist, only use if there's at least 1 item remaining
|
||||
var filteredRewardItemPool = inBudgetRewardItemPool.Where(item => !rewardTplBlacklist.Contains(item.Id)
|
||||
var filteredRewardItemPool = inBudgetRewardItemPool.Where(item =>
|
||||
!rewardTplBlacklist.Contains(item.Id)
|
||||
);
|
||||
if (filteredRewardItemPool.Count() > 0)
|
||||
{
|
||||
@@ -139,7 +161,6 @@ public class RepeatableQuestRewardGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
@@ -159,7 +180,9 @@ public class RepeatableQuestRewardGenerator(
|
||||
// Add item rewards
|
||||
foreach (var itemReward in itemsToReward)
|
||||
{
|
||||
rewards.Success.Add(GenerateItemReward(itemReward.Key.Id, itemReward.Value, rewardIndex));
|
||||
rewards.Success.Add(
|
||||
GenerateItemReward(itemReward.Key.Id, itemReward.Value, rewardIndex)
|
||||
);
|
||||
rewardIndex++;
|
||||
}
|
||||
}
|
||||
@@ -176,19 +199,21 @@ public class RepeatableQuestRewardGenerator(
|
||||
Target = traderId,
|
||||
Value = rewardParams.RewardReputation,
|
||||
Type = RewardType.TraderStanding,
|
||||
Index = rewardIndex
|
||||
Index = rewardIndex,
|
||||
};
|
||||
rewards.Success.Add(reward);
|
||||
rewardIndex++;
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward");
|
||||
_logger.Debug(
|
||||
$"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Chance of adding skill reward
|
||||
if (_randomUtil.GetChance100((double) rewardParams.SkillRewardChance * 100))
|
||||
if (_randomUtil.GetChance100((double)rewardParams.SkillRewardChance * 100))
|
||||
{
|
||||
var targetSkill = _randomUtil.GetArrayValue(eliminationConfig.PossibleSkillRewards);
|
||||
Reward reward = new()
|
||||
@@ -200,20 +225,26 @@ public class RepeatableQuestRewardGenerator(
|
||||
Target = targetSkill,
|
||||
Value = rewardParams.SkillPointReward,
|
||||
Type = RewardType.Skill,
|
||||
Index = rewardIndex
|
||||
Index = rewardIndex,
|
||||
};
|
||||
rewards.Success.Add(reward);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}");
|
||||
_logger.Debug(
|
||||
$"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
}
|
||||
|
||||
protected QuestRewardValues GetQuestRewardValues(RewardScaling? rewardScaling, double? difficulty, int pmcLevel)
|
||||
protected QuestRewardValues GetQuestRewardValues(
|
||||
RewardScaling? rewardScaling,
|
||||
double? difficulty,
|
||||
int pmcLevel
|
||||
)
|
||||
{
|
||||
// difficulty could go from 0.2 ... -> for lowest difficulty receive 0.2*nominal reward
|
||||
var levelsConfig = rewardScaling.Levels;
|
||||
@@ -258,61 +289,102 @@ public class RepeatableQuestRewardGenerator(
|
||||
gpCoinConfig,
|
||||
rewardSpreadConfig
|
||||
),
|
||||
RewardXP = GetRewardXp(effectiveDifficulty, pmcLevel, levelsConfig, xpConfig, rewardSpreadConfig)
|
||||
RewardXP = GetRewardXp(
|
||||
effectiveDifficulty,
|
||||
pmcLevel,
|
||||
levelsConfig,
|
||||
xpConfig,
|
||||
rewardSpreadConfig
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
protected double GetRewardXp(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig,
|
||||
List<double>? xpConfig, double? rewardSpreadConfig)
|
||||
protected double GetRewardXp(
|
||||
double? effectiveDifficulty,
|
||||
int pmcLevel,
|
||||
List<double>? levelsConfig,
|
||||
List<double>? xpConfig,
|
||||
double? rewardSpreadConfig
|
||||
)
|
||||
{
|
||||
return Math.Floor(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig) *
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
effectiveDifficulty
|
||||
* _mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig)
|
||||
* _randomUtil.GetDouble(
|
||||
(double)(1 - rewardSpreadConfig),
|
||||
(double)(1 + rewardSpreadConfig)
|
||||
)
|
||||
?? 0
|
||||
);
|
||||
}
|
||||
|
||||
protected double GetGpCoinRewardCount(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig,
|
||||
protected double GetGpCoinRewardCount(
|
||||
double? effectiveDifficulty,
|
||||
int pmcLevel,
|
||||
List<double>? levelsConfig,
|
||||
List<double>? gpCoinConfig,
|
||||
double? rewardSpreadConfig)
|
||||
double? rewardSpreadConfig
|
||||
)
|
||||
{
|
||||
return Math.Ceiling(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig) *
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
effectiveDifficulty
|
||||
* _mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig)
|
||||
* _randomUtil.GetDouble(
|
||||
(double)(1 - rewardSpreadConfig),
|
||||
(double)(1 + rewardSpreadConfig)
|
||||
)
|
||||
?? 0
|
||||
);
|
||||
}
|
||||
|
||||
protected double GetRewardRep(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig,
|
||||
protected double GetRewardRep(
|
||||
double? effectiveDifficulty,
|
||||
int pmcLevel,
|
||||
List<double>? levelsConfig,
|
||||
List<double>? reputationConfig,
|
||||
double? rewardSpreadConfig)
|
||||
double? rewardSpreadConfig
|
||||
)
|
||||
{
|
||||
return Math.Round(
|
||||
100 *
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig) *
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
) /
|
||||
100;
|
||||
100
|
||||
* effectiveDifficulty
|
||||
* _mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig)
|
||||
* _randomUtil.GetDouble(
|
||||
(double)(1 - rewardSpreadConfig),
|
||||
(double)(1 + rewardSpreadConfig)
|
||||
)
|
||||
?? 0
|
||||
) / 100;
|
||||
}
|
||||
|
||||
protected int GetRewardNumItems(int pmcLevel, List<double>? levelsConfig, List<double>? itemsConfig)
|
||||
protected int GetRewardNumItems(
|
||||
int pmcLevel,
|
||||
List<double>? levelsConfig,
|
||||
List<double>? itemsConfig
|
||||
)
|
||||
{
|
||||
return _randomUtil.RandInt(1, (int) Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig) ?? 0) + 1);
|
||||
return _randomUtil.RandInt(
|
||||
1,
|
||||
(int)Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig) ?? 0) + 1
|
||||
);
|
||||
}
|
||||
|
||||
protected double GetRewardRoubles(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig,
|
||||
protected double GetRewardRoubles(
|
||||
double? effectiveDifficulty,
|
||||
int pmcLevel,
|
||||
List<double>? levelsConfig,
|
||||
List<double>? roublesConfig,
|
||||
double? rewardSpreadConfig)
|
||||
double? rewardSpreadConfig
|
||||
)
|
||||
{
|
||||
return Math.Floor(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) *
|
||||
_randomUtil.GetDouble(1d - rewardSpreadConfig.Value, 1d + rewardSpreadConfig.Value) ??
|
||||
0
|
||||
effectiveDifficulty
|
||||
* _mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig)
|
||||
* _randomUtil.GetDouble(
|
||||
1d - rewardSpreadConfig.Value,
|
||||
1d + rewardSpreadConfig.Value
|
||||
)
|
||||
?? 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -324,8 +396,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="itemRewardBudget"> Rouble budget all item rewards must fit in </param>
|
||||
/// <param name="repeatableConfig"> Config for quest type </param>
|
||||
/// <returns> Dictionary of items and stack size</returns>
|
||||
protected Dictionary<TemplateItem, int> GetRewardableItemsFromPoolWithinBudget(List<TemplateItem> itemPool,
|
||||
int maxItemCount, double itemRewardBudget, RepeatableQuestConfig repeatableConfig)
|
||||
protected Dictionary<TemplateItem, int> GetRewardableItemsFromPoolWithinBudget(
|
||||
List<TemplateItem> itemPool,
|
||||
int maxItemCount,
|
||||
double itemRewardBudget,
|
||||
RepeatableQuestConfig repeatableConfig
|
||||
)
|
||||
{
|
||||
var itemsToReturn = new Dictionary<TemplateItem, int>();
|
||||
var exhausableItemPool = new ExhaustableArray<TemplateItem>(itemPool, _randomUtil, _cloner);
|
||||
@@ -346,7 +422,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
if (_itemHelper.IsOfBaseclass(chosenItemFromPool.Id, BaseClasses.AMMO))
|
||||
{
|
||||
// Don't reward ammo that stacks to less than what's allowed in config
|
||||
if (chosenItemFromPool.Properties.StackMaxSize < repeatableConfig.RewardAmmoStackMinSize)
|
||||
if (
|
||||
chosenItemFromPool.Properties.StackMaxSize
|
||||
< repeatableConfig.RewardAmmoStackMinSize
|
||||
)
|
||||
{
|
||||
i--;
|
||||
continue;
|
||||
@@ -373,7 +452,9 @@ public class RepeatableQuestRewardGenerator(
|
||||
var calculatedItemRewardBudget = itemRewardBudget - rewardItemStackCount * itemCost;
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}");
|
||||
_logger.Debug(
|
||||
$"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}"
|
||||
);
|
||||
}
|
||||
|
||||
// If we still have budget narrow down possible items
|
||||
@@ -390,7 +471,9 @@ public class RepeatableQuestRewardGenerator(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining");
|
||||
_logger.Debug(
|
||||
$"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,8 +493,11 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="roublesBudget"> Rouble budget </param>
|
||||
/// <param name="rewardNumItems"> Count of rewarded items </param>
|
||||
/// <returns> Count that fits budget (min 1) </returns>
|
||||
protected int CalculateAmmoStackSizeThatFitsBudget(TemplateItem itemSelected, double roublesBudget,
|
||||
int rewardNumItems)
|
||||
protected int CalculateAmmoStackSizeThatFitsBudget(
|
||||
TemplateItem itemSelected,
|
||||
double roublesBudget,
|
||||
int rewardNumItems
|
||||
)
|
||||
{
|
||||
// Calculate budget per reward item
|
||||
var stackRoubleBudget = roublesBudget / rewardNumItems;
|
||||
@@ -425,23 +511,22 @@ public class RepeatableQuestRewardGenerator(
|
||||
var stackMaxCount = Math.Min(itemSelected.Properties.StackMaxSize.Value, 100);
|
||||
|
||||
// Ensure stack size is at least 1 + is no larger than the max possible stack size
|
||||
return (int) Math.Max(1, Math.Min(stackSizeThatFitsBudget, stackMaxCount));
|
||||
return (int)Math.Max(1, Math.Min(stackSizeThatFitsBudget, stackMaxCount));
|
||||
}
|
||||
|
||||
protected bool CanIncreaseRewardItemStackSize(TemplateItem item, int maxRoublePriceToStack,
|
||||
int randomChanceToPass = 100)
|
||||
protected bool CanIncreaseRewardItemStackSize(
|
||||
TemplateItem item,
|
||||
int maxRoublePriceToStack,
|
||||
int randomChanceToPass = 100
|
||||
)
|
||||
{
|
||||
var isEligibleForStackSizeIncrease =
|
||||
_presetHelper.GetDefaultPresetOrItemPrice(item.Id) < maxRoublePriceToStack &&
|
||||
!_itemHelper.IsOfBaseclasses(
|
||||
_presetHelper.GetDefaultPresetOrItemPrice(item.Id) < maxRoublePriceToStack
|
||||
&& !_itemHelper.IsOfBaseclasses(
|
||||
item.Id,
|
||||
[
|
||||
BaseClasses.WEAPON,
|
||||
BaseClasses.ARMORED_EQUIPMENT,
|
||||
BaseClasses.AMMO
|
||||
]
|
||||
) &&
|
||||
!_itemHelper.ItemRequiresSoftInserts(item.Id);
|
||||
[BaseClasses.WEAPON, BaseClasses.ARMORED_EQUIPMENT, BaseClasses.AMMO]
|
||||
)
|
||||
&& !_itemHelper.ItemRequiresSoftInserts(item.Id);
|
||||
|
||||
return isEligibleForStackSizeIncrease && _randomUtil.GetChance100(randomChanceToPass);
|
||||
}
|
||||
@@ -460,7 +545,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
{
|
||||
new(3000, [2, 3, 4]),
|
||||
new(10000, [2, 3]),
|
||||
new(int.MaxValue, [2, 3, 4]) // Default for prices 10001+ RUB
|
||||
new(int.MaxValue, [2, 3, 4]), // Default for prices 10001+ RUB
|
||||
};
|
||||
|
||||
// Find the appropriate price tier and return a random stack size from its options
|
||||
@@ -480,8 +565,11 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="roublesBudget"> Total value of items to return </param>
|
||||
/// <param name="traderId"> ID of the trader who will give player reward </param>
|
||||
/// <returns> List of reward items that fit budget </returns>
|
||||
protected List<TemplateItem> ChooseRewardItemsWithinBudget(RepeatableQuestConfig repeatableConfig,
|
||||
double? roublesBudget, string traderId)
|
||||
protected List<TemplateItem> ChooseRewardItemsWithinBudget(
|
||||
RepeatableQuestConfig repeatableConfig,
|
||||
double? roublesBudget,
|
||||
string traderId
|
||||
)
|
||||
{
|
||||
// First filter for type and baseclass to avoid lookup in handbook for non-available items
|
||||
var rewardableItemPool = GetRewardableItems(repeatableConfig, traderId);
|
||||
@@ -498,11 +586,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"repeatable-no_reward_item_found_in_price_range",
|
||||
new
|
||||
{
|
||||
minPrice,
|
||||
roublesBudget
|
||||
}
|
||||
new { minPrice, roublesBudget }
|
||||
)
|
||||
);
|
||||
|
||||
@@ -522,15 +606,18 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="roublesBudget"> The budget remaining for rewards </param>
|
||||
/// <param name="minPrice"> The minimum priced item to include </param>
|
||||
/// <returns> List of Items </returns>
|
||||
protected List<TemplateItem> FilterRewardPoolWithinBudget(List<TemplateItem> rewardItems, double roublesBudget,
|
||||
double minPrice)
|
||||
protected List<TemplateItem> FilterRewardPoolWithinBudget(
|
||||
List<TemplateItem> rewardItems,
|
||||
double roublesBudget,
|
||||
double minPrice
|
||||
)
|
||||
{
|
||||
return rewardItems.Where(item =>
|
||||
{
|
||||
var itemPrice = _presetHelper.GetDefaultPresetOrItemPrice(item.Id);
|
||||
return itemPrice < roublesBudget && itemPrice > minPrice;
|
||||
}
|
||||
)
|
||||
return rewardItems
|
||||
.Where(item =>
|
||||
{
|
||||
var itemPrice = _presetHelper.GetDefaultPresetOrItemPrice(item.Id);
|
||||
return itemPrice < roublesBudget && itemPrice > minPrice;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -540,7 +627,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="roublesBudget"> Budget in roubles </param>
|
||||
/// <param name="rewardIndex"> Index of the reward </param>
|
||||
/// <returns> Dictionary of the reward and it's price, can return null. </returns>
|
||||
protected KeyValuePair<Reward, double>? GetRandomWeaponPresetWithinBudget(double roublesBudget, int rewardIndex)
|
||||
protected KeyValuePair<Reward, double>? GetRandomWeaponPresetWithinBudget(
|
||||
double roublesBudget,
|
||||
int rewardIndex
|
||||
)
|
||||
{
|
||||
// Add a random default preset weapon as reward
|
||||
var defaultPresetPool = new ExhaustableArray<Preset>(
|
||||
@@ -568,7 +658,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
var chosenPreset = _cloner.Clone(randomPreset);
|
||||
|
||||
return new KeyValuePair<Reward, double>(
|
||||
GeneratePresetReward(chosenPreset.Encyclopedia, 1, rewardIndex, chosenPreset.Items),
|
||||
GeneratePresetReward(
|
||||
chosenPreset.Encyclopedia,
|
||||
1,
|
||||
rewardIndex,
|
||||
chosenPreset.Items
|
||||
),
|
||||
presetPrice
|
||||
);
|
||||
}
|
||||
@@ -586,7 +681,13 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="preset"> Optional list of preset items </param>
|
||||
/// <param name="foundInRaid"> If generated Item is found in raid, default True </param>
|
||||
/// <returns> Object of "Reward"-item-type </returns>
|
||||
protected Reward GeneratePresetReward(string tpl, int count, int index, List<Item>? preset, bool foundInRaid = true)
|
||||
protected Reward GeneratePresetReward(
|
||||
string tpl,
|
||||
int count,
|
||||
int index,
|
||||
List<Item>? preset,
|
||||
bool foundInRaid = true
|
||||
)
|
||||
{
|
||||
var id = _hashUtil.Generate();
|
||||
var questRewardItem = new Reward
|
||||
@@ -601,7 +702,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
IsEncoded = false,
|
||||
FindInRaid = foundInRaid,
|
||||
Type = RewardType.Item,
|
||||
Items = []
|
||||
Items = [],
|
||||
};
|
||||
|
||||
// Get presets root item
|
||||
@@ -630,7 +731,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="index"> All rewards will be appended to a list, for unknown reasons the client wants the index</param>
|
||||
/// <param name="foundInRaid"> If generated Item is found in raid, default True </param>
|
||||
/// <returns> Object of "Reward"-item-type </returns>
|
||||
protected Reward GenerateItemReward(string tpl, double count, int index, bool foundInRaid = true)
|
||||
protected Reward GenerateItemReward(
|
||||
string tpl,
|
||||
double count,
|
||||
int index,
|
||||
bool foundInRaid = true
|
||||
)
|
||||
{
|
||||
var id = _hashUtil.Generate();
|
||||
var questRewardItem = new Reward
|
||||
@@ -645,18 +751,14 @@ public class RepeatableQuestRewardGenerator(
|
||||
IsEncoded = false,
|
||||
FindInRaid = foundInRaid,
|
||||
Type = RewardType.Item,
|
||||
Items = []
|
||||
Items = [],
|
||||
};
|
||||
|
||||
var rootItem = new Item
|
||||
{
|
||||
Id = id,
|
||||
Template = tpl,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = count,
|
||||
SpawnedInSession = foundInRaid
|
||||
}
|
||||
Upd = new Upd { StackObjectsCount = count, SpawnedInSession = foundInRaid },
|
||||
};
|
||||
questRewardItem.Items = [rootItem];
|
||||
|
||||
@@ -667,17 +769,20 @@ public class RepeatableQuestRewardGenerator(
|
||||
{
|
||||
// Determine currency based on trader
|
||||
// PK and Fence use Euros, everyone else is Roubles
|
||||
var currency = traderId is Traders.PEACEKEEPER or Traders.FENCE ? Money.EUROS : Money.ROUBLES;
|
||||
var currency = traderId is Traders.PEACEKEEPER or Traders.FENCE
|
||||
? Money.EUROS
|
||||
: Money.ROUBLES;
|
||||
|
||||
// Convert reward amount to Euros if necessary
|
||||
var rewardAmountToGivePlayer =
|
||||
currency == Money.EUROS ? _handbookHelper.FromRUB(rewardRoubles, Money.EUROS) : rewardRoubles;
|
||||
currency == Money.EUROS
|
||||
? _handbookHelper.FromRUB(rewardRoubles, Money.EUROS)
|
||||
: rewardRoubles;
|
||||
|
||||
// Get chosen currency + amount and return
|
||||
return GenerateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Picks rewardable items from items.json <br />
|
||||
/// This means they must: <br />
|
||||
@@ -688,7 +793,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="repeatableQuestConfig"> Config </param>
|
||||
/// <param name="tradderId"> ID of trader who will give reward to player </param>
|
||||
/// <returns> List of rewardable items [[_tpl, itemTemplate],...] </returns>
|
||||
public List<TemplateItem> GetRewardableItems(RepeatableQuestConfig repeatableQuestConfig, string traderId)
|
||||
public List<TemplateItem> GetRewardableItems(
|
||||
RepeatableQuestConfig repeatableQuestConfig,
|
||||
string traderId
|
||||
)
|
||||
{
|
||||
// Get an array of seasonal items that should not be shown right now as seasonal event is not active
|
||||
var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
@@ -696,31 +804,32 @@ public class RepeatableQuestRewardGenerator(
|
||||
// Check for specific base classes which don't make sense as reward item
|
||||
// also check if the price is greater than 0; there are some items whose price can not be found
|
||||
// those are not in the game yet (e.g. AGS grenade launcher)
|
||||
return _databaseService.GetItems()
|
||||
return _databaseService
|
||||
.GetItems()
|
||||
.Values.Where(itemTemplate =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (itemTemplate.Parent == "")
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (itemTemplate.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seasonalItems.Contains(itemTemplate.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(trader => trader.TraderId == traderId
|
||||
);
|
||||
|
||||
return IsValidRewardItem(
|
||||
itemTemplate.Id,
|
||||
repeatableQuestConfig.RewardBlacklist,
|
||||
repeatableQuestConfig.RewardBaseTypeBlacklist,
|
||||
traderWhitelist?.RewardBaseWhitelist
|
||||
);
|
||||
return false;
|
||||
}
|
||||
)
|
||||
|
||||
if (seasonalItems.Contains(itemTemplate.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(trader =>
|
||||
trader.TraderId == traderId
|
||||
);
|
||||
|
||||
return IsValidRewardItem(
|
||||
itemTemplate.Id,
|
||||
repeatableQuestConfig.RewardBlacklist,
|
||||
repeatableQuestConfig.RewardBaseTypeBlacklist,
|
||||
traderWhitelist?.RewardBaseWhitelist
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -733,10 +842,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
/// <param name="itemTypeBlacklist"> Specific item base types to ignore </param>
|
||||
/// <param name="itemBaseWhitelist"> Default null, specific trader item base classes</param>
|
||||
/// <returns> True if item is valid reward </returns>
|
||||
public bool IsValidRewardItem(string tpl,
|
||||
public bool IsValidRewardItem(
|
||||
string tpl,
|
||||
HashSet<string> itemTplBlacklist,
|
||||
HashSet<string> itemTypeBlacklist,
|
||||
List<string>? itemBaseWhitelist = null)
|
||||
List<string>? itemBaseWhitelist = null
|
||||
)
|
||||
{
|
||||
// Return early if not valid item to give as reward
|
||||
if (!_itemHelper.IsValidItem(tpl))
|
||||
@@ -746,10 +857,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
// Check item is not blacklisted
|
||||
if (
|
||||
_itemFilterService.IsItemBlacklisted(tpl) ||
|
||||
_itemFilterService.IsItemRewardBlacklisted(tpl) ||
|
||||
itemTplBlacklist.Contains(tpl) ||
|
||||
_itemFilterService.IsItemBlacklisted(tpl)
|
||||
|| _itemFilterService.IsItemRewardBlacklisted(tpl)
|
||||
|| itemTplBlacklist.Contains(tpl)
|
||||
|| _itemFilterService.IsItemBlacklisted(tpl)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -53,7 +53,10 @@ public class ScavCaseRewardGenerator(
|
||||
// Get items that fit the price criteria as set by the scavCase config
|
||||
var commonPricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Common);
|
||||
var rarePricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Rare);
|
||||
var superRarePricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Superrare);
|
||||
var superRarePricedItems = GetFilteredItemsByPrice(
|
||||
_dbItemsCache,
|
||||
rewardItemCounts.Superrare
|
||||
);
|
||||
|
||||
// Get randomly picked items from each item collection, the count range of which is defined in hideout/scavcase.json
|
||||
var randomlyPickedCommonRewards = PickRandomRewards(
|
||||
@@ -65,7 +68,8 @@ public class ScavCaseRewardGenerator(
|
||||
var randomlyPickedRareRewards = PickRandomRewards(
|
||||
rarePricedItems,
|
||||
rewardItemCounts.Rare,
|
||||
RewardRarity.Rare);
|
||||
RewardRarity.Rare
|
||||
);
|
||||
|
||||
var randomlyPickedSuperRareRewards = PickRandomRewards(
|
||||
superRarePricedItems,
|
||||
@@ -74,9 +78,18 @@ public class ScavCaseRewardGenerator(
|
||||
);
|
||||
|
||||
// Add randomised stack sizes to ammo and money rewards
|
||||
var commonRewards = RandomiseContainerItemRewards(randomlyPickedCommonRewards, RewardRarity.Common);
|
||||
var rareRewards = RandomiseContainerItemRewards(randomlyPickedRareRewards, RewardRarity.Rare);
|
||||
var superRareRewards = RandomiseContainerItemRewards(randomlyPickedSuperRareRewards, RewardRarity.SuperRare);
|
||||
var commonRewards = RandomiseContainerItemRewards(
|
||||
randomlyPickedCommonRewards,
|
||||
RewardRarity.Common
|
||||
);
|
||||
var rareRewards = RandomiseContainerItemRewards(
|
||||
randomlyPickedRareRewards,
|
||||
RewardRarity.Rare
|
||||
);
|
||||
var superRareRewards = RandomiseContainerItemRewards(
|
||||
randomlyPickedSuperRareRewards,
|
||||
RewardRarity.SuperRare
|
||||
);
|
||||
|
||||
var result = new List<List<Item>>();
|
||||
result = result.Concat(commonRewards).ToList();
|
||||
@@ -97,120 +110,131 @@ public class ScavCaseRewardGenerator(
|
||||
var inactiveSeasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
if (!_dbItemsCache.Any())
|
||||
{
|
||||
_dbItemsCache = _databaseService.GetItems()
|
||||
_dbItemsCache = _databaseService
|
||||
.GetItems()
|
||||
.Values.Where(item =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "")
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Type == "Node")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Properties.QuestItem ?? false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip item if item id is on blacklist
|
||||
if (
|
||||
item.Type != "Item" ||
|
||||
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
|
||||
_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Globally reward-blacklisted
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(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 (inactiveSeasonalItems.Contains(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
)
|
||||
|
||||
if (item.Type == "Node")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Properties.QuestItem ?? false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip item if item id is on blacklist
|
||||
if (
|
||||
item.Type != "Item"
|
||||
|| _scavCaseConfig.RewardItemBlacklist.Contains(item.Id)
|
||||
|| _itemFilterService.IsItemBlacklisted(item.Id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Globally reward-blacklisted
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(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 (inactiveSeasonalItems.Contains(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (!_dbAmmoItemsCache.Any())
|
||||
{
|
||||
_dbAmmoItemsCache = _databaseService.GetItems()
|
||||
_dbAmmoItemsCache = _databaseService
|
||||
.GetItems()
|
||||
.Values.Where(item =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "")
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Type != "Item")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not ammo, skip
|
||||
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 (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip seasonal items
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
)
|
||||
|
||||
if (item.Type != "Item")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not ammo, skip
|
||||
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 (
|
||||
!_scavCaseConfig.AllowBossItemsAsRewards
|
||||
&& _itemFilterService.IsBossItem(item.Id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip seasonal items
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -224,13 +248,14 @@ public class ScavCaseRewardGenerator(
|
||||
protected List<TemplateItem> PickRandomRewards(
|
||||
List<TemplateItem> items,
|
||||
RewardCountAndPriceDetails itemFilters,
|
||||
string rarity)
|
||||
string rarity
|
||||
)
|
||||
{
|
||||
List<TemplateItem> result = [];
|
||||
|
||||
var rewardWasMoney = false;
|
||||
var rewardWasAmmo = false;
|
||||
var randomCount = _randomUtil.GetInt((int) itemFilters.MinCount, (int) itemFilters.MaxCount);
|
||||
var randomCount = _randomUtil.GetInt((int)itemFilters.MinCount, (int)itemFilters.MaxCount);
|
||||
for (var i = 0; i < randomCount; i++)
|
||||
{
|
||||
if (RewardShouldBeMoney() && !rewardWasMoney)
|
||||
@@ -301,26 +326,30 @@ public class ScavCaseRewardGenerator(
|
||||
protected TemplateItem GetRandomAmmo(string rarity)
|
||||
{
|
||||
var possibleAmmoPool = _dbAmmoItemsCache.Where(ammo =>
|
||||
{
|
||||
// Is ammo handbook price between desired range
|
||||
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(ammo.Id);
|
||||
if (_scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub.TryGetValue(rarity,
|
||||
out var matchingAmmoRewardForRarity) &&
|
||||
handbookPrice >= matchingAmmoRewardForRarity.Min &&
|
||||
handbookPrice <= matchingAmmoRewardForRarity.Max
|
||||
{
|
||||
// Is ammo handbook price between desired range
|
||||
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(ammo.Id);
|
||||
if (
|
||||
_scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub.TryGetValue(
|
||||
rarity,
|
||||
out var matchingAmmoRewardForRarity
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
&& handbookPrice >= matchingAmmoRewardForRarity.Min
|
||||
&& handbookPrice <= matchingAmmoRewardForRarity.Max
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!possibleAmmoPool.Any())
|
||||
{
|
||||
// Filtered pool is empty
|
||||
_logger.Warning(localisationService.GetText("scavcase-no_cartridges_found_matching_price"));
|
||||
_logger.Warning(
|
||||
localisationService.GetText("scavcase-no_cartridges_found_matching_price")
|
||||
);
|
||||
}
|
||||
|
||||
// Get a random ammo and return it
|
||||
@@ -334,7 +363,10 @@ public class ScavCaseRewardGenerator(
|
||||
/// <param name="rewardItems">items to convert</param>
|
||||
/// <param name="rarity">The rarity desired ammo reward is for</param>
|
||||
/// <returns>Product array</returns>
|
||||
protected List<List<Item>> RandomiseContainerItemRewards(List<TemplateItem> rewardItems, string rarity)
|
||||
protected List<List<Item>> RandomiseContainerItemRewards(
|
||||
List<TemplateItem> rewardItems,
|
||||
string rarity
|
||||
)
|
||||
{
|
||||
// Each array is an item + children
|
||||
List<List<Item>> result = [];
|
||||
@@ -346,8 +378,8 @@ public class ScavCaseRewardGenerator(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = rewardItemDb.Id,
|
||||
Upd = null
|
||||
}
|
||||
Upd = null,
|
||||
},
|
||||
];
|
||||
var rootItem = resultItem.FirstOrDefault();
|
||||
|
||||
@@ -357,14 +389,16 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
// Armor or weapon = use default preset from globals.json
|
||||
else if (
|
||||
_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(rewardItemDb.Id) ||
|
||||
_itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.WEAPON)
|
||||
_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(rewardItemDb.Id)
|
||||
|| _itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.WEAPON)
|
||||
)
|
||||
{
|
||||
var preset = _presetHelper.GetDefaultPreset(rewardItemDb.Id);
|
||||
if (preset is null)
|
||||
{
|
||||
_logger.Warning($"No preset for item: {rewardItemDb.Id} {rewardItemDb.Name}, skipping");
|
||||
_logger.Warning(
|
||||
$"No preset for item: {rewardItemDb.Id} {rewardItemDb.Name}, skipping"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -375,11 +409,13 @@ public class ScavCaseRewardGenerator(
|
||||
|
||||
resultItem = presetAndMods;
|
||||
}
|
||||
else if (_itemHelper.IsOfBaseclasses(rewardItemDb.Id, [BaseClasses.AMMO, BaseClasses.MONEY]))
|
||||
else if (
|
||||
_itemHelper.IsOfBaseclasses(rewardItemDb.Id, [BaseClasses.AMMO, BaseClasses.MONEY])
|
||||
)
|
||||
{
|
||||
rootItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity)
|
||||
StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -396,19 +432,23 @@ public class ScavCaseRewardGenerator(
|
||||
/// <returns>filtered dbItems array</returns>
|
||||
protected List<TemplateItem> GetFilteredItemsByPrice(
|
||||
List<TemplateItem> dbItems,
|
||||
RewardCountAndPriceDetails itemFilters)
|
||||
RewardCountAndPriceDetails itemFilters
|
||||
)
|
||||
{
|
||||
return dbItems.Where(item =>
|
||||
return dbItems
|
||||
.Where(item =>
|
||||
{
|
||||
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id);
|
||||
if (
|
||||
handbookPrice >= itemFilters.MinPriceRub
|
||||
&& handbookPrice <= itemFilters.MaxPriceRub
|
||||
)
|
||||
{
|
||||
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id);
|
||||
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
)
|
||||
|
||||
return false;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -417,7 +457,9 @@ public class ScavCaseRewardGenerator(
|
||||
/// </summary>
|
||||
/// <param name="scavCaseDetails">production.json/scavRecipes object</param>
|
||||
/// <returns>ScavCaseRewardCountsAndPrices object</returns>
|
||||
protected ScavCaseRewardCountsAndPrices GetScavCaseRewardCountsAndPrices(ScavRecipe scavCaseDetails)
|
||||
protected ScavCaseRewardCountsAndPrices GetScavCaseRewardCountsAndPrices(
|
||||
ScavRecipe scavCaseDetails
|
||||
)
|
||||
{
|
||||
return new ScavCaseRewardCountsAndPrices
|
||||
{
|
||||
@@ -427,22 +469,22 @@ public class ScavCaseRewardGenerator(
|
||||
MinCount = scavCaseDetails.EndProducts.Common.Min,
|
||||
MaxCount = scavCaseDetails.EndProducts.Common.Max,
|
||||
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Min,
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Max
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Max,
|
||||
},
|
||||
Rare = new RewardCountAndPriceDetails
|
||||
{
|
||||
MinCount = scavCaseDetails.EndProducts.Rare.Min,
|
||||
MaxCount = scavCaseDetails.EndProducts.Rare.Max,
|
||||
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Min,
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Max
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Max,
|
||||
},
|
||||
Superrare = new RewardCountAndPriceDetails
|
||||
{
|
||||
MinCount = scavCaseDetails.EndProducts.Superrare.Min,
|
||||
MaxCount = scavCaseDetails.EndProducts.Superrare.Max,
|
||||
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Min,
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Max
|
||||
}
|
||||
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Max,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -458,7 +500,7 @@ public class ScavCaseRewardGenerator(
|
||||
{
|
||||
BaseClasses.AMMO => GetRandomisedAmmoRewardStackSize(itemToCalculate),
|
||||
BaseClasses.MONEY => GetRandomisedMoneyRewardStackSize(itemToCalculate, rarity),
|
||||
_ => 1
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -501,7 +543,7 @@ public class ScavCaseRewardGenerator(
|
||||
_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Min,
|
||||
_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Max
|
||||
),
|
||||
_ => 1
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ public class BarrelInvetoryMagGen(
|
||||
// Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot"
|
||||
double? randomisedAmmoStackSize;
|
||||
if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1)
|
||||
// Doesn't stack
|
||||
// Doesn't stack
|
||||
{
|
||||
randomisedAmmoStackSize = _randomUtil.GetInt(3, 6);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class BarrelInvetoryMagGen(
|
||||
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int) randomisedAmmoStackSize,
|
||||
(int)randomisedAmmoStackSize,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
null
|
||||
);
|
||||
|
||||
+28
-14
@@ -42,7 +42,9 @@ public class ExternalInventoryMagGen(
|
||||
var defaultMagazineTpl = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weapon);
|
||||
var isShotgun = _itemHelper.IsOfBaseclass(weapon.Id, BaseClasses.SHOTGUN);
|
||||
|
||||
var randomizedMagazineCount = _botWeaponGeneratorHelper.GetRandomizedMagazineCount(inventoryMagGen.GetMagCount());
|
||||
var randomizedMagazineCount = _botWeaponGeneratorHelper.GetRandomizedMagazineCount(
|
||||
inventoryMagGen.GetMagCount()
|
||||
);
|
||||
for (var i = 0; i < randomizedMagazineCount; i++)
|
||||
{
|
||||
var magazineWithAmmo = _botWeaponGeneratorHelper.CreateMagazineWithAmmo(
|
||||
@@ -60,7 +62,7 @@ public class ExternalInventoryMagGen(
|
||||
);
|
||||
|
||||
if (fitsIntoInventory == ItemAddedResult.NO_CONTAINERS)
|
||||
// No containers to fit magazines, stop trying
|
||||
// No containers to fit magazines, stop trying
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -73,7 +75,9 @@ public class ExternalInventoryMagGen(
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Failed {fitAttempts} times to add magazine {magazineTpl} to bot inventory, stopping");
|
||||
_logger.Debug(
|
||||
$"Failed {fitAttempts} times to add magazine {magazineTpl} to bot inventory, stopping"
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -84,7 +88,7 @@ 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
|
||||
// We were already on default - stop here to prevent infinite loop
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -93,13 +97,13 @@ public class ExternalInventoryMagGen(
|
||||
attemptedMagBlacklist.Add(magazineTpl);
|
||||
|
||||
if (defaultMagazineTpl is null)
|
||||
// No default to fall back to, stop trying to add mags
|
||||
// No default to fall back to, stop trying to add mags
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (defaultMagazineTpl == BaseClasses.MAGAZINE)
|
||||
// Magazine base type, do not use
|
||||
// Magazine base type, do not use
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -110,7 +114,10 @@ public class ExternalInventoryMagGen(
|
||||
if (magTemplate is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("bot-unable_to_find_default_magazine_item", magazineTpl)
|
||||
_localisationService.GetText(
|
||||
"bot-unable_to_find_default_magazine_item",
|
||||
magazineTpl
|
||||
)
|
||||
);
|
||||
|
||||
break;
|
||||
@@ -152,7 +159,7 @@ public class ExternalInventoryMagGen(
|
||||
}
|
||||
|
||||
if (fitsIntoInventory == ItemAddedResult.SUCCESS)
|
||||
// Reset fit counter now it succeeded
|
||||
// Reset fit counter now it succeeded
|
||||
{
|
||||
fitAttempts = 0;
|
||||
}
|
||||
@@ -165,27 +172,34 @@ public class ExternalInventoryMagGen(
|
||||
/// <param name="weaponTpl"> Weapon to get mag for </param>
|
||||
/// <param name="magazineBlacklist"> Blacklisted magazines </param>
|
||||
/// <returns> Item of chosen magazine </returns>
|
||||
public TemplateItem? GetRandomExternalMagazineForInternalMagazineGun(string weaponTpl, List<string> magazineBlacklist)
|
||||
public TemplateItem? GetRandomExternalMagazineForInternalMagazineGun(
|
||||
string weaponTpl,
|
||||
List<string> magazineBlacklist
|
||||
)
|
||||
{
|
||||
// The mag Slot data for the weapon
|
||||
var magSlot = _itemHelper.GetItem(weaponTpl).Value.Properties.Slots.FirstOrDefault(x => x.Name == "mod_magazine");
|
||||
var magSlot = _itemHelper
|
||||
.GetItem(weaponTpl)
|
||||
.Value.Properties.Slots.FirstOrDefault(x => x.Name == "mod_magazine");
|
||||
if (magSlot is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All possible mags that fit into the weapon excluding blacklisted
|
||||
var magazinePool = magSlot.Props.Filters[0]
|
||||
var magazinePool = magSlot
|
||||
.Props.Filters[0]
|
||||
.Filter.Where(x => !magazineBlacklist.Contains(x))
|
||||
.Select(x => _itemHelper.GetItem(x).Value
|
||||
);
|
||||
.Select(x => _itemHelper.GetItem(x).Value);
|
||||
if (magazinePool is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Non-internal magazines that fit into the weapon
|
||||
var externalMagazineOnlyPool = magazinePool.Where(x => x.Properties.ReloadMagType != ReloadMode.InternalMagazine);
|
||||
var externalMagazineOnlyPool = magazinePool.Where(x =>
|
||||
x.Properties.ReloadMagType != ReloadMode.InternalMagazine
|
||||
);
|
||||
if (externalMagazineOnlyPool is null || !externalMagazineOnlyPool.Any())
|
||||
{
|
||||
return null;
|
||||
|
||||
+6
-5
@@ -5,9 +5,9 @@ using SPTarkov.Server.Core.Models.Enums;
|
||||
namespace SPTarkov.Server.Core.Generators.WeaponGen.Implementations;
|
||||
|
||||
[Injectable]
|
||||
public class InternalMagazineInventoryMagGen(
|
||||
BotWeaponGeneratorHelper _botWeaponGeneratorHelper
|
||||
) : InventoryMagGen, IInventoryMagGen
|
||||
public class InternalMagazineInventoryMagGen(BotWeaponGeneratorHelper _botWeaponGeneratorHelper)
|
||||
: InventoryMagGen,
|
||||
IInventoryMagGen
|
||||
{
|
||||
public int GetPriority()
|
||||
{
|
||||
@@ -16,7 +16,8 @@ public class InternalMagazineInventoryMagGen(
|
||||
|
||||
public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen)
|
||||
{
|
||||
return inventoryMagGen.GetMagazineTemplate().Properties.ReloadMagType == ReloadMode.InternalMagazine;
|
||||
return inventoryMagGen.GetMagazineTemplate().Properties.ReloadMagType
|
||||
== ReloadMode.InternalMagazine;
|
||||
}
|
||||
|
||||
public void Process(InventoryMagGen inventoryMagGen)
|
||||
@@ -27,7 +28,7 @@ public class InternalMagazineInventoryMagGen(
|
||||
);
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int) bulletCount,
|
||||
(int)bulletCount,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
null
|
||||
);
|
||||
|
||||
+4
-4
@@ -5,9 +5,9 @@ using SPTarkov.Server.Core.Models.Enums;
|
||||
namespace SPTarkov.Server.Core.Generators.WeaponGen.Implementations;
|
||||
|
||||
[Injectable]
|
||||
public class UbglExternalMagGen(
|
||||
BotWeaponGeneratorHelper _botWeaponGeneratorHelper
|
||||
) : InventoryMagGen, IInventoryMagGen
|
||||
public class UbglExternalMagGen(BotWeaponGeneratorHelper _botWeaponGeneratorHelper)
|
||||
: InventoryMagGen,
|
||||
IInventoryMagGen
|
||||
{
|
||||
public int GetPriority()
|
||||
{
|
||||
@@ -27,7 +27,7 @@ public class UbglExternalMagGen(
|
||||
);
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int) bulletCount,
|
||||
(int)bulletCount,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
[EquipmentSlots.TacticalVest]
|
||||
);
|
||||
|
||||
@@ -12,14 +12,14 @@ public class InventoryMagGen()
|
||||
private readonly BotBaseInventory _pmcInventory;
|
||||
private readonly TemplateItem _weaponTemplate;
|
||||
|
||||
public InventoryMagGen
|
||||
(
|
||||
public InventoryMagGen(
|
||||
GenerationData magCounts,
|
||||
TemplateItem magazineTemplate,
|
||||
TemplateItem weaponTemplate,
|
||||
TemplateItem ammoTemplate,
|
||||
BotBaseInventory pmcInventory
|
||||
) : this()
|
||||
)
|
||||
: this()
|
||||
{
|
||||
_magCounts = magCounts;
|
||||
_magazineTemplate = magazineTemplate;
|
||||
|
||||
@@ -81,16 +81,25 @@ public class WeatherGenerator(
|
||||
Temperature = 0,
|
||||
Fog = GetWeightedFog(weatherValues),
|
||||
RainIntensity =
|
||||
rain > 1 ? GetRandomDouble(weatherValues.RainIntensity.Min, weatherValues.RainIntensity.Max) : 0,
|
||||
rain > 1
|
||||
? GetRandomDouble(
|
||||
weatherValues.RainIntensity.Min,
|
||||
weatherValues.RainIntensity.Max
|
||||
)
|
||||
: 0,
|
||||
Rain = rain,
|
||||
WindGustiness = GetRandomDouble(weatherValues.WindGustiness.Min, weatherValues.WindGustiness.Max, 2),
|
||||
WindGustiness = GetRandomDouble(
|
||||
weatherValues.WindGustiness.Min,
|
||||
weatherValues.WindGustiness.Max,
|
||||
2
|
||||
),
|
||||
WindDirection = GetWeightedWindDirection(weatherValues),
|
||||
WindSpeed = GetWeightedWindSpeed(weatherValues),
|
||||
Cloud = clouds,
|
||||
Time = "",
|
||||
Date = "",
|
||||
Timestamp = 0,
|
||||
SptInRaidTimestamp = 0
|
||||
SptInRaidTimestamp = 0,
|
||||
};
|
||||
|
||||
SetCurrentDateTime(result, timestamp);
|
||||
@@ -102,7 +111,10 @@ public class WeatherGenerator(
|
||||
|
||||
protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason)
|
||||
{
|
||||
var result = _weatherConfig.Weather.SeasonValues.TryGetValue(currentSeason.ToString(), out var value);
|
||||
var result = _weatherConfig.Weather.SeasonValues.TryGetValue(
|
||||
currentSeason.ToString(),
|
||||
out var value
|
||||
);
|
||||
if (!result)
|
||||
{
|
||||
return _weatherConfig.Weather.SeasonValues["default"];
|
||||
@@ -137,9 +149,13 @@ public class WeatherGenerator(
|
||||
{
|
||||
var inRaidTime = timestamp is null
|
||||
? _weatherHelper.GetInRaidTime()
|
||||
: _weatherHelper.GetInRaidTime(timestamp.Value);
|
||||
: _weatherHelper.GetInRaidTime(timestamp.Value);
|
||||
var normalTime = GetBsgFormattedTime(inRaidTime);
|
||||
var formattedDate = _timeUtil.FormatDate(timestamp.HasValue ? _timeUtil.GetDateTimeFromTimeStamp(timestamp.Value) : DateTime.UtcNow);
|
||||
var formattedDate = _timeUtil.FormatDate(
|
||||
timestamp.HasValue
|
||||
? _timeUtil.GetDateTimeFromTimeStamp(timestamp.Value)
|
||||
: DateTime.UtcNow
|
||||
);
|
||||
var datetimeBsgFormat = $"{formattedDate} {normalTime}";
|
||||
|
||||
weather.Timestamp = timestamp ?? _timeUtil.GetTimeStamp(); // matches weather.date
|
||||
@@ -150,17 +166,23 @@ public class WeatherGenerator(
|
||||
|
||||
protected WindDirection GetWeightedWindDirection(SeasonalValues weather)
|
||||
{
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindDirection.Values, weather.WindDirection.Weights).Item;
|
||||
return _weightedRandomHelper
|
||||
.WeightedRandom(weather.WindDirection.Values, weather.WindDirection.Weights)
|
||||
.Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedClouds(SeasonalValues weather)
|
||||
{
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Clouds.Values, weather.Clouds.Weights).Item;
|
||||
return _weightedRandomHelper
|
||||
.WeightedRandom(weather.Clouds.Values, weather.Clouds.Weights)
|
||||
.Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedWindSpeed(SeasonalValues weather)
|
||||
{
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindSpeed.Values, weather.WindSpeed.Weights).Item;
|
||||
return _weightedRandomHelper
|
||||
.WeightedRandom(weather.WindSpeed.Values, weather.WindSpeed.Weights)
|
||||
.Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedFog(SeasonalValues weather)
|
||||
|
||||
Reference in New Issue
Block a user