This commit is contained in:
CWX
2025-01-29 13:35:22 +00:00
35 changed files with 243 additions and 257 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.Controllers;
using Core.Helpers;
using Core.Models.Eft.Common;
@@ -131,9 +131,9 @@ public class ProfileCallbacks(
/**
* Handle client/game/profile/search
*/
public string SearchFriend(string url, SearchFriendRequestData info, string sessionID)
public string SearchProfiles(string url, SearchProfilesRequestData info, string sessionID)
{
return _httpResponse.GetBody(_profileController.GetFriends(info, sessionID));
return _httpResponse.GetBody(_profileController.SearchProfiles(info, sessionID));
}
/**
@@ -496,7 +496,7 @@ public class HideoutController(
recipe.Requirements.Where((r) => r.Type == "Item" || r.Type == "Tool")
);
List<IdWithCount> itemsToDelete = new List<IdWithCount>();
List<IdWithCount> itemsToDelete = [];
var output = _eventOutputHolder.GetOutput(sessionID);
itemsToDelete.AddRange(body.Tools);
itemsToDelete.AddRange(body.Items);
@@ -789,7 +789,7 @@ public class HideoutController(
hoursCrafting += recipe.ProductionTime;
if (hoursCrafting / _hideoutConfig.HoursForSkillCrafting >= 1)
{
// Spent enough time crafting to get a bonus xp multipler
// Spent enough time crafting to get a bonus xp multiplier
var multiplierCrafting = Math.Floor((double)hoursCrafting / _hideoutConfig.HoursForSkillCrafting);
craftingExpAmount += (int)(1 * multiplierCrafting);
hoursCrafting -= _hideoutConfig.HoursForSkillCrafting * multiplierCrafting;
@@ -257,9 +257,9 @@ public class InventoryController(
var rewards = new List<List<Item>>();
var unlockedWeaponCrates = new List<string>
{
"665829424de4820934746ce6",
"665732e7ac60f009f270d1ef",
"665888282c4a1b73af576b77"
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_VIOLET_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_GREEN_OPEN
};
// Temp fix for unlocked weapon crate hideout craft
if (isSealedWeaponBox || unlockedWeaponCrates.Contains(containerDetailsDb.Value.Id))
@@ -177,9 +177,8 @@ public class ProfileController(
/**
* Handle client/game/profile/search
*/
public List<SearchFriendResponse> GetFriends(SearchFriendRequestData info, string sessionID)
public List<SearchFriendResponse> SearchProfiles(SearchProfilesRequestData info, string sessionID)
{
// TODO: We should probably rename this method in the next client update
var result = new List<SearchFriendResponse>();
// Find any profiles with a nickname containing the entered name
@@ -140,7 +140,7 @@ public class RagfairController
var traderAssorts = _ragfairHelper.GetDisplayableAssorts(sessionID);
var result = new GetOffersResult
{
Offers = new List<RagfairOffer>(),
Offers = [],
OffersCount = searchRequest.Limit,
SelectedCategory = searchRequest.HandbookId,
};
@@ -986,7 +986,7 @@ public class RagfairController
)
);
pmcData.RagfairInfo.Offers = new List<RagfairOffer>();
pmcData.RagfairInfo.Offers = [];
}
var playerOfferIndex = playerProfileOffers.FindIndex(offer => offer.Id == removeRequest.OfferId);
@@ -39,6 +39,20 @@ public class BotInventoryGenerator(
{
private BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
// Slots handled individually inside `GenerateAndAddEquipmentToBot`
List<EquipmentSlots> _excludedEquipmentSlots =
[
EquipmentSlots.Pockets,
EquipmentSlots.FirstPrimaryWeapon,
EquipmentSlots.SecondPrimaryWeapon,
EquipmentSlots.Holster,
EquipmentSlots.ArmorVest,
EquipmentSlots.TacticalVest,
EquipmentSlots.FaceCover,
EquipmentSlots.Headwear,
EquipmentSlots.Earpiece
];
/// <summary>
/// Add equipment/weapons/loot to bot
/// </summary>
@@ -142,20 +156,6 @@ public class BotInventoryGenerator(
public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole,
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig)
{
// These will be handled later
var excludedSlots = new List<EquipmentSlots>()
{
EquipmentSlots.Pockets,
EquipmentSlots.FirstPrimaryWeapon,
EquipmentSlots.SecondPrimaryWeapon,
EquipmentSlots.Holster,
EquipmentSlots.ArmorVest,
EquipmentSlots.TacticalVest,
EquipmentSlots.FaceCover,
EquipmentSlots.Headwear,
EquipmentSlots.Earpiece
};
_botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig);
var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig);
@@ -188,7 +188,7 @@ public class BotInventoryGenerator(
{
// Skip some slots as they need to be done in a specific order + with specific parameter values
// e.g. Weapons
if (excludedSlots.Contains(equipmentSlot))
if (_excludedEquipmentSlots.Contains(equipmentSlot))
{
continue;
}
@@ -394,7 +394,7 @@ public class BotWeaponGenerator(
return;
}
var isInternalMag = magTemplate.Properties.ReloadMagType == "InternalMagazine";
var isInternalMag = magTemplate.Properties.ReloadMagType == ReloadMode.InternalMagazine;
var ammoTemplate = _itemHelper.GetItem(generatedWeaponResult.ChosenAmmoTemplate).Value;
if (ammoTemplate is null)
{
@@ -508,7 +508,7 @@ public class BotWeaponGenerator(
{
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
// return default mag tpl
if (weaponTemplate.Properties.ReloadMode == "OnlyBarrel")
if (weaponTemplate.Properties.ReloadMode == ReloadMode.OnlyBarrel)
{
return _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
}
+1 -1
View File
@@ -586,7 +586,7 @@ public class LootGenerator(
// Find a random item of the desired type and add as reward
for (var index = 0; index < rewardCount; index++) {
var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList());
var reward = new List<Item> { new Item() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id } };
var reward = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id } };
modRewards.Add(reward);
}
@@ -151,7 +151,7 @@ public class PlayerScavGenerator(
var itemTemplate = itemResult.Value;
var itemsToAdd = new List<Item>()
{
new Item()
new()
{
Id = _hashUtil.Generate(),
Template = itemTemplate.Id,
@@ -515,7 +515,7 @@ public class RepeatableQuestGenerator(
);
// Be fair, don't var the items be more expensive than the reward
var multi = _randomUtil.GetFloat((float)0.5, 1);
var multi = _randomUtil.GetDouble(0.5, 1);
var roublesBudget = Math.Floor(
(double)(_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multi)
);
@@ -264,7 +264,7 @@ public class RepeatableQuestRewardGenerator(
return Math.Floor(
effectiveDifficulty *
_mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig) *
_randomUtil.GetFloat((float)(1 - rewardSpreadConfig), (float)(1 + rewardSpreadConfig)) ??
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
0
);
}
@@ -276,7 +276,7 @@ public class RepeatableQuestRewardGenerator(
return Math.Ceiling(
effectiveDifficulty *
_mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig) *
_randomUtil.GetFloat((float)(1 - rewardSpreadConfig), (float)(1 + rewardSpreadConfig)) ??
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
0
);
}
@@ -289,7 +289,7 @@ public class RepeatableQuestRewardGenerator(
100 *
effectiveDifficulty *
_mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig) *
_randomUtil.GetFloat((float)(1 - rewardSpreadConfig), (float)(1 + rewardSpreadConfig)) ??
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
0
) /
100;
@@ -307,7 +307,7 @@ public class RepeatableQuestRewardGenerator(
return Math.Floor(
effectiveDifficulty *
_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) *
_randomUtil.GetFloat((float)(1 - rewardSpreadConfig), (float)(1 + rewardSpreadConfig)) ??
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
0
);
}
@@ -1,4 +1,4 @@
using Core.Helpers;
using Core.Helpers;
using Core.Models.Common;
using SptCommon.Annotations;
using Core.Models.Eft.Common.Tables;
@@ -32,8 +32,8 @@ public class ScavCaseRewardGenerator(
)
{
protected ScavCaseConfig _scavCaseConfig = _configServer.GetConfig<ScavCaseConfig>();
protected List<TemplateItem> _dbItemsCache = new List<TemplateItem>();
protected List<TemplateItem> _dbAmmoItemsCache = new List<TemplateItem>();
protected List<TemplateItem> _dbItemsCache = [];
protected List<TemplateItem> _dbAmmoItemsCache = [];
/// <summary>
/// Create an array of rewards that will be given to the player upon completing their scav case build
@@ -1,5 +1,6 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.Helpers;
using Core.Models.Enums;
using Core.Utils;
namespace Core.Generators.WeaponGen.Implementations;
@@ -17,7 +18,7 @@ public class BarrelInvetoryMagGen(
public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen)
{
return inventoryMagGen.GetWeaponTemplate().Properties.ReloadMode == "OnlyBarrel";
return inventoryMagGen.GetWeaponTemplate().Properties.ReloadMode == ReloadMode.OnlyBarrel;
}
public void Process(InventoryMagGen inventoryMagGen)
@@ -118,7 +118,7 @@ public class ExternalInventoryMagGen(
}
// Edge case - some weapons (SKS + shotguns) have an internal magazine as default, choose random non-internal magazine to add to bot instead
if (magTemplate.Properties.ReloadMagType == "InternalMagazine")
if (magTemplate.Properties.ReloadMagType == ReloadMode.InternalMagazine)
{
var result = GetRandomExternalMagazineForInternalMagazineGun(
inventoryMagGen.GetWeaponTemplate().Id,
@@ -179,7 +179,7 @@ public class ExternalInventoryMagGen(
}
// Non-internal magazines that fit into the weapon
var externalMagazineOnlyPool = magazinePool.Where((x) => x.Properties.ReloadMagType != "InternalMagazine");
var externalMagazineOnlyPool = magazinePool.Where((x) => x.Properties.ReloadMagType != ReloadMode.InternalMagazine);
if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0)
{
return null;
@@ -1,5 +1,6 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.Helpers;
using Core.Models.Enums;
namespace Core.Generators.WeaponGen.Implementations;
@@ -15,7 +16,7 @@ public class InternalMagazineInventoryMagGen(
public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen)
{
return inventoryMagGen.GetMagazineTemplate().Properties.ReloadMagType == "InternalMagazine";
return inventoryMagGen.GetMagazineTemplate().Properties.ReloadMagType == ReloadMode.InternalMagazine;
}
public void Process(InventoryMagGen inventoryMagGen)
+3 -6
View File
@@ -72,7 +72,7 @@ public class DialogueHelper(
// Check reward count when item being moved isn't in reward list
// If count is 0, it means after this move occurs the reward array will be empty and all rewards collected
if (message.Items.Data is null)
message.Items.Data = new();
message.Items.Data = [];
var rewardItems = message.Items.Data?.Where(x => x.Id != itemId);
if (rewardItems.Count() == 0)
@@ -85,7 +85,7 @@ public class DialogueHelper(
}
}
return new List<Item>();
return [];
}
/// <summary>
@@ -96,10 +96,7 @@ public class DialogueHelper(
public Dictionary<string, Models.Eft.Profile.Dialogue> GetDialogsForProfile(string sessionId)
{
var profile = _profileHelper.GetFullProfile(sessionId);
if (profile.DialogueRecords is null)
profile.DialogueRecords = new();
return profile.DialogueRecords;
return profile.DialogueRecords ?? (profile.DialogueRecords = new());
}
public Models.Eft.Profile.Dialogue? GetDialogueFromProfile(string profileId, string dialogueId)
+8 -9
View File
@@ -40,6 +40,9 @@ public class InventoryHelper(
{
protected InventoryConfig _inventoryConfig = _configServer.GetConfig<InventoryConfig>();
// Item types to ignore inside `GetSizeByInventoryItemHash`
List<string> _itemBaseTypesToIgnore = [BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER];
/// <summary>
/// Add multiple items to player stash (assuming they all fit)
/// </summary>
@@ -655,12 +658,8 @@ public class InventoryHelper(
var forcedDown = 0;
var forcedLeft = 0;
var forcedRight = 0;
var outX = (int)tmpItem.Properties.Width;
var outY = (int)tmpItem.Properties.Height;
// Item types to ignore
var skipThisItems = new List<string>
{ BaseClasses.BACKPACK, BaseClasses.SEARCHABLE_ITEM, BaseClasses.SIMPLE_CONTAINER };
var outX = tmpItem.Properties.Width;
var outY = tmpItem.Properties.Height;
var rootIsFolded = rootItem?.Upd?.Foldable?.Folded == true;
@@ -669,7 +668,7 @@ public class InventoryHelper(
outX -= tmpItem.Properties.SizeReduceRight.Value;
// Calculate size contribution from child items/attachments
if (!skipThisItems.Contains(tmpItem.Parent))
if (!_itemBaseTypesToIgnore.Contains(tmpItem.Parent))
while (toDo.Count > 0)
{
if (inventoryItemHash.ByParentId.ContainsKey(toDo[0]))
@@ -726,8 +725,8 @@ public class InventoryHelper(
return
[
outX + sizeLeft + sizeRight + forcedLeft + forcedRight,
outY + sizeUp + sizeDown + forcedUp + forcedDown
outX.Value + sizeLeft + sizeRight + forcedLeft + forcedRight,
outY.Value + sizeUp + sizeDown + forcedUp + forcedDown
];
}
+2 -2
View File
@@ -2045,8 +2045,8 @@ public class ItemHelper(
public class ItemSize
{
[JsonPropertyName("width")]
public double Width { get; set; }
public int Width { get; set; }
[JsonPropertyName("height")]
public double Height { get; set; }
public int Height { get; set; }
}
+2 -2
View File
@@ -124,7 +124,7 @@ public class RepairHelper(
? armorMaterialSettings.MaxRepairKitDegradation
: armorMaterialSettings.MaxRepairDegradation;
var duraLossPercent = _randomUtil.GetFloat((float)minMultiplier, (float)maxMultiplier);
var duraLossPercent = _randomUtil.GetDouble((double)minMultiplier, (double)maxMultiplier);
var duraLossMultipliedByTraderMultiplier = duraLossPercent * armorMax * traderQualityMultipler;
return Math.Round(duraLossMultipliedByTraderMultiplier, 2);
@@ -153,7 +153,7 @@ public class RepairHelper(
maxRepairDeg = itemProps.MaxRepairDegradation;
}
var duraLossPercent = _randomUtil.GetFloat((float)minRepairDeg, (float)maxRepairDeg);
var duraLossPercent = _randomUtil.GetDouble((double)minRepairDeg, (double)maxRepairDeg);
var duraLossMultipliedByTraderMultiplier = duraLossPercent * weaponMax * traderQualityMultipler;
return Math.Round(duraLossMultipliedByTraderMultiplier, 2);
+5 -5
View File
@@ -170,16 +170,16 @@ public class TradeHelper(
if (assortHasBuyRestrictions)
{
var itemPurchaseDat = new PurchaseDetails()
var itemPurchaseDat = new PurchaseDetails
{
Items = new List<PurchaseItems>()
{
new PurchaseItems()
Items =
[
new PurchaseItems
{
ItemId = buyRequestData.ItemId,
Count = buyCount
}
},
],
TraderId = buyRequestData.TransactionId
};
+1 -3
View File
@@ -582,9 +582,7 @@ public class TraderHelper(
var traderBuyBackPricePercent = traderBase.LoyaltyLevels.FirstOrDefault().BuyPriceCoefficient;
var itemHandbookPrice = _handbookHelper.GetTemplatePrice(tpl);
var priceTraderBuysItemAt = Math.Round(
_randomUtil.GetPercentOfValue(traderBuyBackPricePercent ?? 0, itemHandbookPrice ?? 0)
);
var priceTraderBuysItemAt = _randomUtil.GetPercentOfValue(traderBuyBackPricePercent ?? 0, itemHandbookPrice ?? 0, 0);
// Price from this trader is higher than highest found, update
if (priceTraderBuysItemAt > highestPrice)
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using Core.Models.Enums;
using Core.Utils.Json.Converters;
namespace Core.Models.Eft.Common.Tables;
@@ -56,17 +57,21 @@ public record Props
[JsonPropertyName("BackgroundColor")]
public string? BackgroundColor { get; set; }
// Type confirmed via client
[JsonPropertyName("Width")]
public double? Width { get; set; }
public int? Width { get; set; }
// Type confirmed via client
[JsonPropertyName("Height")]
public double? Height { get; set; }
public int? Height { get; set; }
// Type confirmed via client
[JsonPropertyName("StackMaxSize")]
public int? StackMaxSize { get; set; }
// Type confirmed via client
[JsonPropertyName("Rarity")]
public string? Rarity { get; set; }
public LootRarity? Rarity { get; set; }
[JsonPropertyName("SpawnChance")]
public double? SpawnChance { get; set; }
@@ -134,10 +139,11 @@ public record Props
[JsonPropertyName("QuestStashMaxCount")]
public double? QuestStashMaxCount { get; set; }
// Type confirmed via client
[JsonPropertyName("LootExperience")]
public double? LootExperience { get; set; }
public int? LootExperience { get; set; }
// Checked on live
// Type confirmed via client
[JsonPropertyName("ExamineExperience")]
public int? ExamineExperience { get; set; }
@@ -147,11 +153,13 @@ public record Props
[JsonPropertyName("InsuranceDisabled")]
public bool? InsuranceDisabled { get; set; }
// Type confirmed via client
[JsonPropertyName("RepairCost")]
public double? RepairCost { get; set; }
public int? RepairCost { get; set; }
// Type confirmed via client
[JsonPropertyName("RepairSpeed")]
public double? RepairSpeed { get; set; }
public int? RepairSpeed { get; set; }
[JsonPropertyName("ExtraSizeLeft")]
public int? ExtraSizeLeft { get; set; }
@@ -201,8 +209,9 @@ public record Props
[JsonPropertyName("UnlootableFromSide")]
public List<string>? UnlootableFromSide { get; set; }
// Type confirmed via client
[JsonPropertyName("AnimationVariantsNumber")]
public double? AnimationVariantsNumber { get; set; }
public int? AnimationVariantsNumber { get; set; }
[JsonPropertyName("DiscardingBlock")]
public bool? DiscardingBlock { get; set; }
@@ -222,8 +231,9 @@ public record Props
[JsonPropertyName("DiscardLimit")]
public double? DiscardLimit { get; set; }
// Type confirmed via client
[JsonPropertyName("MaxResource")]
public double? MaxResource { get; set; }
public int? MaxResource { get; set; }
[JsonPropertyName("Resource")]
public double? Resource { get; set; }
@@ -457,7 +467,7 @@ public record Props
public double? CheckOverride { get; set; }
[JsonPropertyName("ReloadMagType")]
public string? ReloadMagType { get; set; }
public ReloadMode? ReloadMagType { get; set; }
[JsonPropertyName("VisibleAmmoRangesString")]
public string? VisibleAmmoRangesString { get; set; }
@@ -546,9 +556,10 @@ public record Props
[JsonPropertyName("armorZone")]
public List<string>? ArmorZone { get; set; }
// Type confirmed via client
[JsonPropertyName("armorClass")]
[JsonConverter(typeof(StringToNumberFactoryConverter))]
public double? ArmorClass { get; set; } // TODO: object here
public int? ArmorClass { get; set; }
[JsonPropertyName("armorColliders")]
public List<string>? ArmorColliders { get; set; }
@@ -683,7 +694,7 @@ public record Props
public double? CameraToWeaponAngleStep { get; set; }
[JsonPropertyName("ReloadMode")]
public string? ReloadMode { get; set; }
public ReloadMode? ReloadMode { get; set; }
[JsonPropertyName("AimPlane")]
public double? AimPlane { get; set; }
@@ -986,8 +997,9 @@ public record Props
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public Dictionary<string, EffectDamageProps>? EffectsDamage { get; set; }
// Confirmed in client
[JsonPropertyName("MaximumNumberOfUsage")]
public double? MaximumNumberOfUsage { get; set; }
public int? MaximumNumberOfUsage { get; set; }
[JsonPropertyName("knifeHitDelay")]
public double? KnifeHitDelay { get; set; }
@@ -1067,8 +1079,9 @@ public record Props
[JsonPropertyName("medEffectType")]
public string? MedEffectType { get; set; }
// Confirmed in client
[JsonPropertyName("MaxHpResource")]
public double? MaxHpResource { get; set; }
public int? MaxHpResource { get; set; }
[JsonPropertyName("hpResourceRate")]
public double? HpResourceRate { get; set; }
@@ -1082,8 +1095,9 @@ public record Props
[JsonPropertyName("MaxOpticZoom")]
public double? MaxOpticZoom { get; set; }
// Confirmed in client
[JsonPropertyName("MaxRepairResource")]
public double? MaxRepairResource { get; set; }
public int? MaxRepairResource { get; set; }
[JsonPropertyName("TargetItemFilter")]
public List<string>? TargetItemFilter { get; set; }
@@ -1127,8 +1141,9 @@ public record Props
[JsonPropertyName("buckshotBullets")]
public double? BuckshotBullets { get; set; }
// Confirmed in client
[JsonPropertyName("PenetrationPower")]
public double? PenetrationPower { get; set; }
public int? PenetrationPower { get; set; }
[JsonPropertyName("PenetrationPowerDeviation")]
public double? PenetrationPowerDeviation { get; set; }
@@ -3,7 +3,7 @@ using Core.Models.Utils;
namespace Core.Models.Eft.Profile;
public record SearchFriendRequestData : IRequestData
public record SearchProfilesRequestData : IRequestData
{
[JsonPropertyName("nickname")]
public string? Nickname { get; set; }
+3
View File
@@ -48,6 +48,9 @@ public class HttpRouter
wrapper.Output = wrapper.Output.Replace(sessionID, sessionID);
}
//var filepath = $"c:\\SharpServer\\{req.Path.ToString().Substring(1).Replace("/", ".")}.json";
//File.WriteAllText(filepath, wrapper.Output);
return wrapper.Output;
}
@@ -1,4 +1,4 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.DI;
using Core.Helpers;
using Core.Models.Eft.Common.Tables;
@@ -11,7 +11,7 @@ public class HealthSaveLoadRouter() : SaveLoadRouter
{
protected override List<HandledRoute> GetHandledRoutes()
{
return new List<HandledRoute>() { new HandledRoute("spt-health", false) };
return [new("spt-health", false)];
}
public override SptProfile HandleLoad(SptProfile profile)
@@ -1,4 +1,4 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.DI;
using Core.Models.Eft.Profile;
@@ -9,7 +9,7 @@ public class InraidSaveLoadRouter : SaveLoadRouter
{
protected override List<HandledRoute> GetHandledRoutes()
{
return new List<HandledRoute>() { new HandledRoute("spt-inraid", false) };
return [new("spt-inraid", false)];
}
public override SptProfile HandleLoad(SptProfile profile)
@@ -1,4 +1,4 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.DI;
using Core.Models.Eft.Profile;
@@ -9,7 +9,7 @@ public class InsuranceSaveLoadRouter : SaveLoadRouter
{
protected override List<HandledRoute> GetHandledRoutes()
{
return new List<HandledRoute>() { new HandledRoute("spt-insurance", false) };
return [new ("spt-insurance", false)];
}
public override SptProfile HandleLoad(SptProfile profile)
@@ -1,4 +1,4 @@
using SptCommon.Annotations;
using SptCommon.Annotations;
using Core.DI;
using Core.Models.Eft.Profile;
@@ -9,7 +9,7 @@ public class ProfileSaveLoadRouter : SaveLoadRouter
{
protected override List<HandledRoute> GetHandledRoutes()
{
return new List<HandledRoute>() { new HandledRoute("spt-profile", false) };
return [new ("spt-profile", false)];
}
public override SptProfile HandleLoad(SptProfile profile)
@@ -103,8 +103,8 @@ public class ProfileStaticRouter : StaticRouter
info,
sessionID,
output
) => profileCallbacks.SearchFriend(url, info as SearchFriendRequestData, sessionID),
typeof(SearchFriendRequestData)),
) => profileCallbacks.SearchProfiles(url, info as SearchProfilesRequestData, sessionID),
typeof(SearchProfilesRequestData)),
new RouteAction(
"/launcher/profile/info",
(url, info, sessionID, output) =>
@@ -126,9 +126,9 @@ public class CircleOfCultistService(
private double GetRewardAmountMultiplier(PmcData pmcData, CultistCircleSettings cultistCircleSettings)
{
// Get a randomised value to multiply the sacrificed rouble cost by
var rewardAmountMultiplier = _randomUtil.GetFloat(
(float)cultistCircleSettings.RewardPriceMultiplerMinMax.Min,
(float)cultistCircleSettings.RewardPriceMultiplerMinMax.Max
var rewardAmountMultiplier = _randomUtil.GetDouble(
(double)cultistCircleSettings.RewardPriceMultiplerMinMax.Min,
(double)cultistCircleSettings.RewardPriceMultiplerMinMax.Max
);
// Adjust value generated by the players hideout management skill
+1 -3
View File
@@ -529,9 +529,7 @@ public class RepairService(
Rarity = bonusRarityName,
BuffType = bonusTypeName,
Value = bonusValue,
ThresholdDurability = Math.Round(
_randomUtil.GetPercentOfValue(bonusThresholdPercent, item.Upd.Repairable.Durability.Value)
)
ThresholdDurability = _randomUtil.GetPercentOfValue(bonusThresholdPercent, item.Upd.Repairable.Durability.Value, 0)
};
}
+42 -87
View File
@@ -11,6 +11,8 @@ namespace Core.Utils;
public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
{
public readonly Random Random = new();
private const int DecimalPointRandomPrecision = 6;
private static readonly int DecimalPointRandomPrecisionMultiplier = (int) Math.Pow(10, DecimalPointRandomPrecision);
/// <summary>
/// The IEEE-754 standard for double-precision floating-point numbers limits the number of digits (including both
@@ -22,40 +24,20 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// Generates a random integer between the specified minimum and maximum values, inclusive.
/// </summary>
/// <param name="min">The minimum value (inclusive).</param>
/// <param name="max">The maximum value (inclusive).</param>
/// <param name="max">The maximum value (optional).</param>
/// <param name="exclusive">If max is exclusive or not.</param>
/// <returns>A random integer between the specified minimum and maximum values.</returns>
public int GetInt(int min, int max)
public int GetInt(int min, int max = int.MaxValue, bool exclusive = false)
{
// Prevents a potential integer overflow.
if (max == int.MaxValue) max -= 1;
if (exclusive && max == int.MaxValue)
{
max -= 1;
}
// maxVal is exclusive of the passed value, so add 1
return max > min ? Random.Next(min, max + 1) : min;
return max > min ? Random.Next(min, exclusive ? max : max + 1) : min;
}
/// <summary>
/// Generates a random integer between 1 (inclusive) and the specified maximum value (exclusive).
/// If the maximum value is less than or equal to 1, it returns 1.
/// </summary>
/// <param name="max">The upper bound (exclusive) for the random integer generation.</param>
/// <returns>A random integer between 1 and max - 1, or 1 if max is less than or equal to 1.</returns>
public int GetIntEx(int max)
{
return max > 2 ? Random.Next(1, max - 1) : 1;
}
/// <summary>
/// Generates a random floating-point number within the specified range ~6-9 digits (4 bytes).
/// </summary>
/// <param name="min">The minimum value of the range (inclusive).</param>
/// <param name="max">The maximum value of the range (exclusive).</param>
/// <returns>A random floating-point number between `min` (inclusive) and `max` (exclusive).</returns>
public float GetFloat(float min, float max)
{
return (float)GetSecureRandomNumber() * (max - min) + min;
}
/// <summary>
/// Generates a random floating-point number within the specified range ~15-17 digits (8 bytes).
/// </summary>
@@ -64,7 +46,13 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// <returns>A random floating-point number between `min` (inclusive) and `max` (exclusive).</returns>
public double GetDouble(double min, double max)
{
return GetSecureRandomNumber() * (max - min) + min;
var realMin = (long) (min * DecimalPointRandomPrecisionMultiplier);
var realMax = (long) (max * DecimalPointRandomPrecisionMultiplier);
return Math.Round(
Random.NextInt64(realMin, realMax) / (double)DecimalPointRandomPrecisionMultiplier,
DecimalPointRandomPrecision
);
}
/// <summary>
@@ -73,7 +61,7 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// <returns>A random boolean value, where the probability of `true` and `false` is approximately equal.</returns>
public bool GetBool()
{
return GetSecureRandomNumber() < 0.5;
return Random.Next(0, 2) == 1;
}
/// <summary>
@@ -83,11 +71,11 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// <param name="number">The number to calculate the percentage of.</param>
/// <param name="toFixed">The number of decimal places to round the result to (default is 2).</param>
/// <returns>The calculated percentage of the given number, rounded to the specified number of decimal places.</returns>
public float GetPercentOfValue(double percent, double number, int toFixed = 2)
public double GetPercentOfValue(double percent, double number, int toFixed = 2)
{
var num = percent * number / 100;
return (float)Math.Round(num, toFixed);
return Math.Round(num, toFixed);
}
/// <summary>
@@ -110,9 +98,9 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// <returns>`true` if the event occurs, `false` otherwise.</returns>
public bool GetChance100(double? chancePercent)
{
chancePercent = Math.Clamp(chancePercent ?? 0, 0f, 100f);
chancePercent = Math.Clamp(chancePercent ?? 0, 0D, 100D);
return GetIntEx(100) <= chancePercent;
return GetInt(0, 100) <= chancePercent;
}
/// <summary>
@@ -196,7 +184,7 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
// Check if the generated value is valid
if (valueDrawn < 0)
return attempt > 100
? GetDouble(0.01f, mean * 2f)
? GetDouble(0.01D, mean * 2D)
: GetNormallyDistributedRandomNumber(mean, sigma, attempt + 1);
return valueDrawn;
@@ -229,42 +217,20 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// and MaxSignificantDigits(15), inclusive. If not provided, precision is determined by the input values.
/// </param>
/// <returns></returns>
public double RandNum(double val1, double val2 = 0, byte? precision = null)
public double RandNum(double val1, double val2 = 0, int precision = DecimalPointRandomPrecision)
{
if (!double.IsFinite(val1) || !double.IsFinite(val2)) throw new ArgumentException("RandNum() parameters 'value1' and 'value2' must be finite numbers.");
// Determine the range
var min = Math.Min(val1, val2);
var max = Math.Max(val1, val2);
// Validate and adjust precision
if (precision is not null)
{
if (precision > MaxSignificantDigits)
throw new ArgumentOutOfRangeException(
nameof(precision), "Must be less than 16");
// Calculate the number of whole-number digits in the maximum absolute value of the range
var maxAbsoluteValue = Math.Max(Math.Abs(min), Math.Abs(max));
var wholeNumberDigits = (int)Math.Floor(Math.Log10(maxAbsoluteValue)) + 1;
var maxAllowedPrecision = Math.Max(0, MaxSignificantDigits - wholeNumberDigits);
if (precision > maxAllowedPrecision)
throw new ArgumentException(
$"RandNum() precision of {precision} exceeds the allowable precision ({maxAllowedPrecision}) for the given values."
);
}
var result = GetSecureRandomNumber() * (max - min) + min;
// Determine effective precision
var maxPrecision = Math.Max(GetNumberPrecision(val1), GetNumberPrecision(val2));
var effectivePrecision = precision ?? maxPrecision;
var factor = Math.Pow(2, effectivePrecision);
return Math.Round(result * factor) / factor;
var realPrecision = (long) Math.Pow(10, precision);
var minInt = (long) (min * realPrecision);
var maxInt = (long) (max * realPrecision);
return Math.Round(Random.NextInt64(minInt, maxInt) / (double) realPrecision, precision);
}
/// <summary>
@@ -436,23 +402,9 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// produce a floating-point number in the range [0, 1).
/// </summary>
/// <returns>A secure random number between 0 (inclusive) and 1 (exclusive).</returns>
private static double GetSecureRandomNumber()
private double GetSecureRandomNumber()
{
var buffer = new byte[6]; // 48 bits
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(buffer);
}
// Convert byte array to unsigned long
ulong value = 0;
for (var i = 0; i < buffer.Length; i++)
{
value |= (ulong)buffer[i] << (8 * (buffer.Length - 1 - i));
}
const ulong maxInteger = 281474976710656; // 2^48
return (double)value / maxInteger;
return Random.NextSingle();
}
/// <summary>
@@ -462,11 +414,14 @@ public class RandomUtil(ISptLogger<RandomUtil> _logger, ICloner _cloner)
/// <returns>The number of decimal places, or 0 if none exist.</returns>
public int GetNumberPrecision(double num)
{
var parts = num.ToString($"G{MaxSignificantDigits}").Split('.');
return parts.Length > 1
? parts[1].Length
: 0;
var preciseNum = (decimal)num;
var factor = 0;
while ((double)(preciseNum % 1) > double.Epsilon)
{
preciseNum *= 10M;
factor++;
}
return factor;
}
public T? GetArrayValue<T>(IEnumerable<T> list)
+1 -1
View File
@@ -14,7 +14,7 @@ public class Test
public void Setup()
{
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(), new JsonUtil());
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(new MockLogger<FileUtil>()), new JsonUtil());
var loadTask = importer.LoadRecursiveAsync<Templates>("./TestAssets/");
loadTask.Wait();
_templates = loadTask.Result;
+57 -48
View File
@@ -1,5 +1,6 @@
using Core.Utils;
using Core.Utils.Cloners;
using System.Net.Mail;
using UnitTests.Mock;
namespace UnitTests.Tests.Utils;
@@ -7,52 +8,60 @@ namespace UnitTests.Tests.Utils;
[TestClass]
public class HashUtilTests
{
protected HashUtil _hashUtil = new(new RandomUtil(new MockLogger<RandomUtil>(), new JsonCloner(new JsonUtil())));
[TestMethod]
public void GenerateTest()
{
// Generate 100 MongoId's
for (var i = 0; i < 100; i++)
{
// Invalid mongoId character
var result = _hashUtil.Generate();
// Invalid mongoId length
var test = _hashUtil.IsValidMongoId(result);
Assert.AreEqual(
true,
test,
$"IsValidMongoId() `{result}` is not a valid MongoId.");
}
}
[TestMethod]
public void IsValidMongoIdTest()
{
// Invalid mongoId character
var ResultBadChar = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbz");
Assert.AreEqual(
false,
ResultBadChar,
"IsValidMongoId() `677ddb67406e9918a0264bbz` contains invalid char `z`, but result was true");
// Invalid mongoId length
var resultBadLength = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbcc");
Assert.AreEqual(
false,
resultBadLength,
"IsValidMongoId() `677ddb67406e9918a0264bbcc` is 25 characters, but result was true");
// Valid mongoId
var resultPass = _hashUtil.IsValidMongoId("677ddb67406e9918a0264bbc");
Assert.AreEqual(
true,
resultPass,
"IsValidMongoId() `677ddb67406e9918a0264bbc` is a valid mongoId, but result was false");
}
protected HashUtil _hashUtil = new(new RandomUtil(new MockLogger<RandomUtil>(), new JsonCloner(new JsonUtil())));
[TestMethod]
public void GenerateTest()
{
// Generate 100 MongoId's
for (var i = 0; i < 100; i++)
{
// Invalid mongoId character
var result = _hashUtil.Generate();
// Invalid mongoId length
var test = _hashUtil.IsValidMongoId(result);
Assert.AreEqual(
true,
test,
$"IsValidMongoId() `{result}` is not a valid MongoId."
);
}
}
[TestMethod]
[DataRow(
"677ddb67406e9918a0264bbz",
false,
"677ddb67406e9918a0264bbz contains invalid char `z`, but result was true"
)]
[DataRow("677ddb67406e9918a0264bbcc", false, "677ddb67406e9918a0264bbcc is 25 characters, but result was true")]
[DataRow(
"677ddb67406e9918a0264bbc",
true,
"IsValidMongoId() `677ddb67406e9918a0264bbc` is a valid mongoId, but result was false"
)]
public void IsValidMongoIdTest(string mongoId, bool passes, string failMessage)
{
var result = _hashUtil.IsValidMongoId(mongoId);
Assert.AreEqual(
passes,
result,
failMessage
);
}
[TestMethod]
[DataRow("123456789", "25F9E794323B453885F5181F1B624D0B", "Not valid output, expected '25F9E794323B453885F5181F1B624D0B'")]
public void GenerateValidMd5Test(string input, string expectedOutput, string failMessage)
{
var result = _hashUtil.GenerateMd5ForData(input);
Assert.AreEqual(
expectedOutput,
result,
failMessage
);
}
}
+27 -16
View File
@@ -12,8 +12,8 @@ public sealed class RandomUtilTests
[TestMethod]
public void GetIntTest()
{
// Run 100 test cases
for (var i = 0; i < 100; i++)
// Run 10000 test cases
for (var i = 0; i < 10000; i++)
{
var result = _randomUtil.GetInt(0, 10);
@@ -27,10 +27,10 @@ public sealed class RandomUtilTests
[TestMethod]
public void GetIntExTest()
{
// Run 100 test cases
for (var i = 0; i < 100; i++)
// Run 10000 test cases
for (var i = 0; i < 10000; i++)
{
var result = _randomUtil.GetIntEx(10);
var result = _randomUtil.GetInt(1, 10, true);
if (result < 1 || result > 9)
{
@@ -40,16 +40,16 @@ public sealed class RandomUtilTests
}
[TestMethod]
public void GetFloatTest()
public void GetDoubleTest()
{
// Run 100 test cases
for (var i = 0; i < 100; i++)
// Run 10000 test cases
for (var i = 0; i < 10000; i++)
{
var result = _randomUtil.GetFloat(0f, 10f);
var result = _randomUtil.GetDouble(0D, 10D);
if (result < 0f || result >= 9f)
if (result is < 0d or >= 10d)
{
Assert.Fail($"GetFloat(0f, 10f) out of range. Expected range [0.0f, 9.999f] but was {result}.");
Assert.Fail($"GetDouble(0d, 10d) out of range. Expected range [0.0d, 9.999d] but was {result}.");
}
}
}
@@ -135,11 +135,11 @@ public sealed class RandomUtilTests
[TestMethod]
public void RandNumTest()
{
for (var i = 0; i < 100; i++)
for (var i = 0; i < 10000; i++)
{
var result = _randomUtil.RandNum(0, 10);
var result = _randomUtil.RandNum(0, 10, 15);
if (result < 0 || result > 9)
if (result < 0 || result >= 10)
{
Assert.Fail($"RandNum(0, 10) out of range. Expected range [0, 9.999d] but was {result}.");
}
@@ -150,11 +150,11 @@ public sealed class RandomUtilTests
}
}
for (var i = 0; i < 100; i++)
for (var i = 0; i < 10000; i++)
{
var result = _randomUtil.RandNum(10);
if (result < 0 || result > 9)
if (result < 0 || result >= 10)
{
Assert.Fail($"RandNum(10) out of range. Expected range [0, 9.999d] but was {result}.");
}
@@ -182,4 +182,15 @@ public sealed class RandomUtilTests
result.SequenceEqual(orig),
$"Shuffle test failed. Expected: {string.Join(", ", orig)}, but got {string.Join(", ", result)}");
}
[TestMethod]
[DataRow(0.1, 1)]
[DataRow(0.0001, 4)]
[DataRow(0, 0)]
[DataRow(10000000, 0)]
[DataRow(0.000_000_000_000_000_000_000_000_1D, 25)]
public void GetNumberPrecision_WithDoubles_ReturnsDecimalPoints(double value, int decimalPoints)
{
Assert.AreEqual(decimalPoints, _randomUtil.GetNumberPrecision(value));
}
}