diff --git a/Benchmarks/MathUtilInterpBenchmarks.cs b/Benchmarks/MathUtilInterpBenchmarks.cs index 7945d059..87ec2ee2 100644 --- a/Benchmarks/MathUtilInterpBenchmarks.cs +++ b/Benchmarks/MathUtilInterpBenchmarks.cs @@ -1,28 +1,27 @@ using BenchmarkDotNet.Attributes; using SPTarkov.Server.Core.Utils; -namespace Benchmarks +namespace Benchmarks; + +[SimpleJob(warmupCount: 10, iterationCount: 25)] +[MemoryDiagnoser] +public class MathUtilInterpBenchmarks { - [SimpleJob(warmupCount: 10, iterationCount: 25)] - [MemoryDiagnoser] - public class MathUtilInterpBenchmarks + private MathUtil _mathUtil; + + private double input = 15d; + private List x = [1, 10, 20, 30, 40, 50, 60]; + private List y = [11000, 20000, 32000, 45000, 58000, 70000, 82000]; + + [GlobalSetup] + public void Setup() { - private MathUtil _mathUtil; + _mathUtil = new MathUtil(); + } - private double input = 15d; - private List x = [1, 10, 20, 30, 40, 50, 60]; - private List y = [11000, 20000, 32000, 45000, 58000, 70000, 82000]; - - [GlobalSetup] - public void Setup() - { - _mathUtil = new MathUtil(); - } - - [Benchmark] - public void Interp() - { - _mathUtil.Interp1(input, x, y); - } + [Benchmark] + public void Interp() + { + _mathUtil.Interp1(input, x, y); } } diff --git a/Libraries/SPTarkov.Reflection/Patching/Attributes.cs b/Libraries/SPTarkov.Reflection/Patching/Attributes.cs index f539d407..42c5c6fd 100644 --- a/Libraries/SPTarkov.Reflection/Patching/Attributes.cs +++ b/Libraries/SPTarkov.Reflection/Patching/Attributes.cs @@ -1,17 +1,16 @@ -namespace SPTarkov.Reflection.Patching -{ - [AttributeUsage(AttributeTargets.Method)] - public class PatchPrefixAttribute : Attribute { } +namespace SPTarkov.Reflection.Patching; - [AttributeUsage(AttributeTargets.Method)] - public class PatchPostfixAttribute : Attribute { } +[AttributeUsage(AttributeTargets.Method)] +public class PatchPrefixAttribute : Attribute { } - [AttributeUsage(AttributeTargets.Method)] - public class PatchTranspilerAttribute : Attribute { } +[AttributeUsage(AttributeTargets.Method)] +public class PatchPostfixAttribute : Attribute { } - [AttributeUsage(AttributeTargets.Method)] - public class PatchFinalizerAttribute : Attribute { } +[AttributeUsage(AttributeTargets.Method)] +public class PatchTranspilerAttribute : Attribute { } - [AttributeUsage(AttributeTargets.Method)] - public class PatchIlManipulatorAttribute : Attribute { } -} +[AttributeUsage(AttributeTargets.Method)] +public class PatchFinalizerAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Method)] +public class PatchIlManipulatorAttribute : Attribute { } diff --git a/Libraries/SPTarkov.Server.Core/DI/ServiceLocator.cs b/Libraries/SPTarkov.Server.Core/DI/ServiceLocator.cs index 114b3b5f..a2265fa9 100644 --- a/Libraries/SPTarkov.Server.Core/DI/ServiceLocator.cs +++ b/Libraries/SPTarkov.Server.Core/DI/ServiceLocator.cs @@ -1,18 +1,17 @@ -namespace SPTarkov.Server.Core.DI -{ - /// - /// A service locator designed specifically for Harmony patches and other - /// parts of the application that do not have direct access to the Dependency Injection (DI) system. - /// - /// This should not be used at all when having direct access to DI. - /// - public static class ServiceLocator - { - public static IServiceProvider ServiceProvider { get; private set; } +namespace SPTarkov.Server.Core.DI; - internal static void SetServiceProvider(IServiceProvider provider) - { - ServiceProvider = provider; - } +/// +/// A service locator designed specifically for Harmony patches and other +/// parts of the application that do not have direct access to the Dependency Injection (DI) system. +/// +/// This should not be used at all when having direct access to DI. +/// +public static class ServiceLocator +{ + public static IServiceProvider ServiceProvider { get; private set; } + + internal static void SetServiceProvider(IServiceProvider provider) + { + ServiceProvider = provider; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs index 4a00bed0..da2d70ff 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ContainerExtensions.cs @@ -1,253 +1,252 @@ using SPTarkov.Server.Core.Models.Spt.Inventory; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class ContainerExtensions { - public static class ContainerExtensions + /// + /// Finds a slot for an item in a given 2D container map + /// + /// List of container with positions filled/free + /// Width of item + /// Height of item + /// Location to place item in container + public static FindSlotResult FindSlotForItem(this int[,] container2D, int? itemWidthX, int? itemHeightY) { - /// - /// Finds a slot for an item in a given 2D container map - /// - /// List of container with positions filled/free - /// Width of item - /// Height of item - /// Location to place item in container - public static FindSlotResult FindSlotForItem(this int[,] container2D, int? itemWidthX, int? itemHeightY) + // Assume not rotated + var rotation = false; + + // Find the min volume the item will take up + var minVolume = (itemWidthX < itemHeightY ? itemWidthX : itemHeightY) - 1; + var containerY = container2D.GetLength(0); // rows + var containerX = container2D.GetLength(1); // columns + var limitY = containerY - minVolume; + var limitX = containerX - minVolume; + + // Every x+y slot taken up in container, exit + if (ContainerIsFull(container2D)) { - // Assume not rotated - var rotation = false; - - // Find the min volume the item will take up - var minVolume = (itemWidthX < itemHeightY ? itemWidthX : itemHeightY) - 1; - var containerY = container2D.GetLength(0); // rows - var containerX = container2D.GetLength(1); // columns - var limitY = containerY - minVolume; - var limitX = containerX - minVolume; - - // Every x+y slot taken up in container, exit - if (ContainerIsFull(container2D)) - { - return new FindSlotResult(false); - } - - // Down = y, iterate over rows - for (var row = 0; row < limitY; row++) - { - if (RowIsFull(container2D, row)) - { - continue; - } - - // Left to right across columns, look for free position - for (var column = 0; column < limitX; column++) - { - // Does item fit - if (CanItemBePlacedInContainerAtPosition(container2D, row, column, itemWidthX.Value, itemHeightY.Value)) - { - // Success, found a spot it fits - return new FindSlotResult(true, column, row, rotation); - } - - if (!ItemBiggerThan1X1(itemWidthX.Value, itemHeightY.Value)) - { - // Doesn't fit AND rotating won't help - continue; - } - - // Rotate item by swapping x and y item values - if ( - CanItemBePlacedInContainerAtPosition( - container2D, - row, - column, - itemHeightY.Value, // Swapped - itemWidthX.Value // Swapped - ) - ) - { - // Found a position for the item when rotated - rotation = true; - return new FindSlotResult(true, column, row, rotation); - } - } - } - - // Tried all possible positions, nothing big enough for item return new FindSlotResult(false); } - /// - /// Find a free slot for an item to be placed at - /// - /// Container to place item in - /// Container y size - /// Container x size - /// Items width - /// Items height - /// is item rotated - public static void FillContainerMapWithItem( - this int[,] container2D, - int columnStartPositionX, - int rowStartPositionY, - int? itemXWidth, - int? itemYHeight, - bool isRotated - ) + // Down = y, iterate over rows + for (var row = 0; row < limitY; row++) { - var containerY = container2D.GetLength(0); // rows - var containerX = container2D.GetLength(1); // columns - - // Swap height/width if item needs to be rotated to fit - var itemWidth = isRotated ? itemYHeight : itemXWidth; - var itemHeight = isRotated ? itemXWidth : itemYHeight; - - var itemRowEndPosition = rowStartPositionY + (itemHeight - 1); - var itemColumnEndPosition = columnStartPositionX + (itemWidth - 1); - - //Item is a 1x1, flag slot as taken and exit early - if (itemXWidth == 1 && itemYHeight == 1) + if (RowIsFull(container2D, row)) { - container2D[rowStartPositionY, columnStartPositionX] = 1; - - return; + continue; } - // Loop over rows and columns and flag each as taken by item - for (var y = rowStartPositionY; y <= itemRowEndPosition; y++) + // Left to right across columns, look for free position + for (var column = 0; column < limitX; column++) { - for (var x = columnStartPositionX; x <= itemColumnEndPosition; x++) + // Does item fit + if (CanItemBePlacedInContainerAtPosition(container2D, row, column, itemWidthX.Value, itemHeightY.Value)) { - if (container2D[y, x] == 0) - { - // Flag slot as used - container2D[y, x] = 1; - } - else - { - throw new Exception( - $"Slot at: ({containerX}, {containerY}) is already filled. Cannot fit: {itemXWidth} by {itemYHeight} item" - ); - } + // Success, found a spot it fits + return new FindSlotResult(true, column, row, rotation); + } + + if (!ItemBiggerThan1X1(itemWidthX.Value, itemHeightY.Value)) + { + // Doesn't fit AND rotating won't help + continue; + } + + // Rotate item by swapping x and y item values + if ( + CanItemBePlacedInContainerAtPosition( + container2D, + row, + column, + itemHeightY.Value, // Swapped + itemWidthX.Value // Swapped + ) + ) + { + // Found a position for the item when rotated + rotation = true; + return new FindSlotResult(true, column, row, rotation); } } } - /// - /// Is the requested row full - /// - /// Container to check - /// Index of row to check - /// True = full - private static bool RowIsFull(int[,] container2D, int rowIndex) - { - var rowFull = true; - var containerColumnCount = container2D.GetLength(1); // Column - for (var col = 0; col < containerColumnCount; col++) - { - if (container2D[rowIndex, col] == 0) - { - rowFull = false; - break; - } - } + // Tried all possible positions, nothing big enough for item + return new FindSlotResult(false); + } - return rowFull; + /// + /// Find a free slot for an item to be placed at + /// + /// Container to place item in + /// Container y size + /// Container x size + /// Items width + /// Items height + /// is item rotated + public static void FillContainerMapWithItem( + this int[,] container2D, + int columnStartPositionX, + int rowStartPositionY, + int? itemXWidth, + int? itemYHeight, + bool isRotated + ) + { + var containerY = container2D.GetLength(0); // rows + var containerX = container2D.GetLength(1); // columns + + // Swap height/width if item needs to be rotated to fit + var itemWidth = isRotated ? itemYHeight : itemXWidth; + var itemHeight = isRotated ? itemXWidth : itemYHeight; + + var itemRowEndPosition = rowStartPositionY + (itemHeight - 1); + var itemColumnEndPosition = columnStartPositionX + (itemWidth - 1); + + //Item is a 1x1, flag slot as taken and exit early + if (itemXWidth == 1 && itemYHeight == 1) + { + container2D[rowStartPositionY, columnStartPositionX] = 1; + + return; } - /// - /// Is every slot in container full - /// - /// Container to check - /// True = full - private static bool ContainerIsFull(int[,] container2D) + // Loop over rows and columns and flag each as taken by item + for (var y = rowStartPositionY; y <= itemRowEndPosition; y++) { - var containerY = container2D.GetLength(0); // rows - var containerX = container2D.GetLength(1); // columns - var containerFull = true; - for (var y = 0; y < containerY; y++) + for (var x = columnStartPositionX; x <= itemColumnEndPosition; x++) { - for (var x = 0; x < containerX; x++) + if (container2D[y, x] == 0) { - if (container2D[y, x] == 0) - { - containerFull = false; - break; - } + // Flag slot as used + container2D[y, x] = 1; } - if (!containerFull) + else { - break; + throw new Exception( + $"Slot at: ({containerX}, {containerY}) is already filled. Cannot fit: {itemXWidth} by {itemYHeight} item" + ); } } - - return containerFull; - } - - /// - /// Is the item size values passed in bigger than 1x1 - /// - /// Width of item - /// Height of item - /// True = bigger than 1x1 - private static bool ItemBiggerThan1X1(int itemWidth, int itemHeight) - { - return itemWidth + itemHeight > 2; - } - - /// - /// Can an item of specified size be placed inside a 2d container at a specific position - /// - /// Container to find space in - /// Starting y position for item - /// Starting x position for item - /// Items width (y) - /// Items height (x) - /// True - slot found - public static bool CanItemBePlacedInContainerAtPosition( - this int[,] container, - int itemStartVerticalPos, - int itemStartHorizontalPos, - int itemWidth, - int itemHeight - ) - { - var containerHeight = container.GetLength(0); // Rows - var containerWidth = container.GetLength(1); // Columns - - var itemEndColPosition = itemStartHorizontalPos + itemWidth - 1; - var itemEndRowPosition = itemStartVerticalPos + itemHeight - 1; - - // Check item isn't bigger than container when at position - if (itemEndColPosition > containerWidth - 1 || itemEndRowPosition > containerHeight - 1) - { - // Item is bigger than container, will never fit - return false; - } - - // Early exit if exact spot is taken - if (container[itemStartVerticalPos, itemStartHorizontalPos] == 1) - { - return false; - } - - // Single slot item, do direct check - if (itemWidth == 1 && itemHeight == 1) - { - return container[itemStartVerticalPos, itemStartHorizontalPos] == 0; - } - - for (var row = itemStartVerticalPos; row <= itemEndRowPosition; row++) - { - for (var column = itemStartHorizontalPos; column <= itemEndColPosition; column++) - { - if (container[row, column] == 1) - { - // Occupied by something - return false; - } - } - } - - return true; // Slot is free } } + + /// + /// Is the requested row full + /// + /// Container to check + /// Index of row to check + /// True = full + private static bool RowIsFull(int[,] container2D, int rowIndex) + { + var rowFull = true; + var containerColumnCount = container2D.GetLength(1); // Column + for (var col = 0; col < containerColumnCount; col++) + { + if (container2D[rowIndex, col] == 0) + { + rowFull = false; + break; + } + } + + return rowFull; + } + + /// + /// Is every slot in container full + /// + /// Container to check + /// True = full + private static bool ContainerIsFull(int[,] container2D) + { + var containerY = container2D.GetLength(0); // rows + var containerX = container2D.GetLength(1); // columns + var containerFull = true; + for (var y = 0; y < containerY; y++) + { + for (var x = 0; x < containerX; x++) + { + if (container2D[y, x] == 0) + { + containerFull = false; + break; + } + } + if (!containerFull) + { + break; + } + } + + return containerFull; + } + + /// + /// Is the item size values passed in bigger than 1x1 + /// + /// Width of item + /// Height of item + /// True = bigger than 1x1 + private static bool ItemBiggerThan1X1(int itemWidth, int itemHeight) + { + return itemWidth + itemHeight > 2; + } + + /// + /// Can an item of specified size be placed inside a 2d container at a specific position + /// + /// Container to find space in + /// Starting y position for item + /// Starting x position for item + /// Items width (y) + /// Items height (x) + /// True - slot found + public static bool CanItemBePlacedInContainerAtPosition( + this int[,] container, + int itemStartVerticalPos, + int itemStartHorizontalPos, + int itemWidth, + int itemHeight + ) + { + var containerHeight = container.GetLength(0); // Rows + var containerWidth = container.GetLength(1); // Columns + + var itemEndColPosition = itemStartHorizontalPos + itemWidth - 1; + var itemEndRowPosition = itemStartVerticalPos + itemHeight - 1; + + // Check item isn't bigger than container when at position + if (itemEndColPosition > containerWidth - 1 || itemEndRowPosition > containerHeight - 1) + { + // Item is bigger than container, will never fit + return false; + } + + // Early exit if exact spot is taken + if (container[itemStartVerticalPos, itemStartHorizontalPos] == 1) + { + return false; + } + + // Single slot item, do direct check + if (itemWidth == 1 && itemHeight == 1) + { + return container[itemStartVerticalPos, itemStartHorizontalPos] == 0; + } + + for (var row = itemStartVerticalPos; row <= itemEndRowPosition; row++) + { + for (var column = itemStartHorizontalPos; column <= itemEndColPosition; column++) + { + if (container[row, column] == 1) + { + // Occupied by something + return false; + } + } + } + + return true; // Slot is free + } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/CurrencyTypeExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/CurrencyTypeExtensions.cs index b4233c3d..571c8be2 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/CurrencyTypeExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/CurrencyTypeExtensions.cs @@ -1,25 +1,24 @@ using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class CurrencyTypeExtensions { - public static class CurrencyTypeExtensions + /// + /// Gets currency TPL from TAG + /// + /// + /// Tpl of currency + public static MongoId GetCurrencyTpl(this CurrencyType currency) { - /// - /// Gets currency TPL from TAG - /// - /// - /// Tpl of currency - public static MongoId GetCurrencyTpl(this CurrencyType currency) + return currency switch { - return currency switch - { - CurrencyType.EUR => Money.EUROS, - CurrencyType.USD => Money.DOLLARS, - CurrencyType.RUB => Money.ROUBLES, - CurrencyType.GP => Money.GP, - _ => string.Empty, - }; - } + CurrencyType.EUR => Money.EUROS, + CurrencyType.USD => Money.DOLLARS, + CurrencyType.RUB => Money.ROUBLES, + CurrencyType.GP => Money.GP, + _ => string.Empty, + }; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs index a7a3b26d..aaef9bc8 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs @@ -1,141 +1,140 @@ -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class DateTimeExtensions { - public static class DateTimeExtensions + /// + /// Formats the time part of a date as a UTC string. + /// + /// The date to format in UTC. + /// The formatted time as 'HH-MM-SS'. + public static string FormatToBsgTime(this DateTimeOffset dateTimeOffset) { - /// - /// Formats the time part of a date as a UTC string. - /// - /// The date to format in UTC. - /// The formatted time as 'HH-MM-SS'. - public static string FormatToBsgTime(this DateTimeOffset dateTimeOffset) - { - var universalTime = dateTimeOffset.ToUniversalTime(); - var hour = Pad(universalTime.Hour); - var minute = Pad(universalTime.Minute); - var second = Pad(universalTime.Second); + var universalTime = dateTimeOffset.ToUniversalTime(); + var hour = Pad(universalTime.Hour); + var minute = Pad(universalTime.Minute); + var second = Pad(universalTime.Second); - return $"{hour}-{minute}-{second}"; + return $"{hour}-{minute}-{second}"; + } + + /// + /// Formats the time part of a date as a UTC string. + /// + /// The date to format in UTC. + /// The formatted time as 'HH-MM-SS'. + public static string FormatToBsgTime(this DateTime dateTime) + { + var universalTime = dateTime.ToUniversalTime(); + var hour = Pad(universalTime.Hour); + var minute = Pad(universalTime.Minute); + var second = Pad(universalTime.Second); + + return $"{hour}-{minute}-{second}"; + } + + /// + /// Formats the date part of a date as a UTC string. + /// + /// The date to format in UTC. + /// The formatted date as 'YYYY-MM-DD'. + public static string FormatToBsgDate(this DateTimeOffset dateTimeOffset) + { + var universalTime = dateTimeOffset.ToUniversalTime(); + var day = Pad(universalTime.Day); + var month = Pad(universalTime.Month); + var year = Pad(universalTime.Year); + + return $"{year}-{month}-{day}"; + } + + /// + /// Formats the date part of a date as a UTC string. + /// + /// The date to format in UTC. + /// The formatted date as 'YYYY-MM-DD'. + public static string FormatToBsgDate(this DateTime dateTime) + { + var universalTime = dateTime.ToUniversalTime(); + var day = Pad(universalTime.Day); + var month = Pad(universalTime.Month); + var year = Pad(universalTime.Year); + + return $"{year}-{month}-{day}"; + } + + /// + /// Pads a number with a leading zero if it is less than 10. + /// + /// The number to pad. + /// The padded number as a string. + private static string Pad(int number) + { + return number.ToString().PadLeft(2, '0'); + } + + /// + /// Get current time formatted to fit BSGs requirement + /// + /// Date to format into bsg style + /// Time formatted in BSG format + public static string GetBsgFormattedWeatherTime(this DateTime date) + { + return date.FormatToBsgTime().Replace("-", ":").Replace("-", ":"); + } + + /// + /// Does the provided date fit between the two defined dates? + /// Excludes year + /// Inclusive of end date up to 23 hours 59 minutes + /// + /// Date to check is between 2 dates + /// Lower bound for month + /// Lower bound for day + /// Upper bound for month + /// Upper bound for day + /// True when inside date range + public static bool DateIsBetweenTwoDates(this DateTime dateToCheck, int startMonth, int startDay, int endMonth, int endDay) + { + var eventStartDate = new DateTime(dateToCheck.Year, startMonth, startDay); + var eventEndDate = new DateTime(dateToCheck.Year, endMonth, endDay, 23, 59, 0); + + return dateToCheck >= eventStartDate && dateToCheck <= eventEndDate; + } + + /// + /// Get the closest monday to passed in datetime + /// + /// Date to get closest monday of + /// Starting day of week - Default = Monday + /// Monday as DateTime + public static DateTime GetClosestDate(this DateTime dateTime, DayOfWeek startDay = DayOfWeek.Monday) + { + // Calculate difference from current day to Monday + var diff = (7 + (dateTime.DayOfWeek - startDay)) % 7; + + // Subtract difference to get date of most recent Monday + return dateTime.AddDays(-1 * diff).Date; + } + + /// + /// Get the most recent requested day from date + /// + /// Date to start from + /// Desired day to find + /// Should today be included in check, default = true + /// Datetime of desired day + public static DateTime GetMostRecentPreviousDay(this DateTime dateTime, DayOfWeek desiredDay, bool inclusiveOfToday = true) + { + // Get difference in day count from today to what day we want + var dayDifferenceCount = (dateTime.DayOfWeek - desiredDay + 7) % 7; + + // Today is wanted day + we are not counting today, we know desired day is exactly 7 days ago + if (!inclusiveOfToday && dayDifferenceCount == 0) + { + dayDifferenceCount = 7; } - /// - /// Formats the time part of a date as a UTC string. - /// - /// The date to format in UTC. - /// The formatted time as 'HH-MM-SS'. - public static string FormatToBsgTime(this DateTime dateTime) - { - var universalTime = dateTime.ToUniversalTime(); - var hour = Pad(universalTime.Hour); - var minute = Pad(universalTime.Minute); - var second = Pad(universalTime.Second); - - return $"{hour}-{minute}-{second}"; - } - - /// - /// Formats the date part of a date as a UTC string. - /// - /// The date to format in UTC. - /// The formatted date as 'YYYY-MM-DD'. - public static string FormatToBsgDate(this DateTimeOffset dateTimeOffset) - { - var universalTime = dateTimeOffset.ToUniversalTime(); - var day = Pad(universalTime.Day); - var month = Pad(universalTime.Month); - var year = Pad(universalTime.Year); - - return $"{year}-{month}-{day}"; - } - - /// - /// Formats the date part of a date as a UTC string. - /// - /// The date to format in UTC. - /// The formatted date as 'YYYY-MM-DD'. - public static string FormatToBsgDate(this DateTime dateTime) - { - var universalTime = dateTime.ToUniversalTime(); - var day = Pad(universalTime.Day); - var month = Pad(universalTime.Month); - var year = Pad(universalTime.Year); - - return $"{year}-{month}-{day}"; - } - - /// - /// Pads a number with a leading zero if it is less than 10. - /// - /// The number to pad. - /// The padded number as a string. - private static string Pad(int number) - { - return number.ToString().PadLeft(2, '0'); - } - - /// - /// Get current time formatted to fit BSGs requirement - /// - /// Date to format into bsg style - /// Time formatted in BSG format - public static string GetBsgFormattedWeatherTime(this DateTime date) - { - return date.FormatToBsgTime().Replace("-", ":").Replace("-", ":"); - } - - /// - /// Does the provided date fit between the two defined dates? - /// Excludes year - /// Inclusive of end date up to 23 hours 59 minutes - /// - /// Date to check is between 2 dates - /// Lower bound for month - /// Lower bound for day - /// Upper bound for month - /// Upper bound for day - /// True when inside date range - public static bool DateIsBetweenTwoDates(this DateTime dateToCheck, int startMonth, int startDay, int endMonth, int endDay) - { - var eventStartDate = new DateTime(dateToCheck.Year, startMonth, startDay); - var eventEndDate = new DateTime(dateToCheck.Year, endMonth, endDay, 23, 59, 0); - - return dateToCheck >= eventStartDate && dateToCheck <= eventEndDate; - } - - /// - /// Get the closest monday to passed in datetime - /// - /// Date to get closest monday of - /// Starting day of week - Default = Monday - /// Monday as DateTime - public static DateTime GetClosestDate(this DateTime dateTime, DayOfWeek startDay = DayOfWeek.Monday) - { - // Calculate difference from current day to Monday - var diff = (7 + (dateTime.DayOfWeek - startDay)) % 7; - - // Subtract difference to get date of most recent Monday - return dateTime.AddDays(-1 * diff).Date; - } - - /// - /// Get the most recent requested day from date - /// - /// Date to start from - /// Desired day to find - /// Should today be included in check, default = true - /// Datetime of desired day - public static DateTime GetMostRecentPreviousDay(this DateTime dateTime, DayOfWeek desiredDay, bool inclusiveOfToday = true) - { - // Get difference in day count from today to what day we want - var dayDifferenceCount = (dateTime.DayOfWeek - desiredDay + 7) % 7; - - // Today is wanted day + we are not counting today, we know desired day is exactly 7 days ago - if (!inclusiveOfToday && dayDifferenceCount == 0) - { - dayDifferenceCount = 7; - } - - // Remove count of day difference to get desired day - return dateTime.AddDays(-dayDifferenceCount); - } + // Remove count of day difference to get desired day + return dateTime.AddDays(-dayDifferenceCount); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/DictionaryExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/DictionaryExtensions.cs index f2cea7b7..d39c4dcf 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/DictionaryExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/DictionaryExtensions.cs @@ -1,45 +1,44 @@ -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class DictionaryExtensions { - public static class DictionaryExtensions + /// + /// Add a value by key to a dictionary, if the key doesn't exist, create it + /// + /// Dictionary key type + /// Dictionary to add/update + /// Key to update by + /// Value to add to key + public static void AddOrUpdate(this IDictionary dict, T key, double value) + where T : notnull { - /// - /// Add a value by key to a dictionary, if the key doesn't exist, create it - /// - /// Dictionary key type - /// Dictionary to add/update - /// Key to update by - /// Value to add to key - public static void AddOrUpdate(this IDictionary dict, T key, double value) - where T : notnull + if (!dict.TryAdd(key, value)) { - if (!dict.TryAdd(key, value)) - { - dict[key] += value; - } + dict[key] += value; } + } - /// - /// Add a value by key to a dictionary, if the key doesn't exist, create it - /// - /// Dictionary key type - /// Dictionary to add/update - /// Key to update by - /// Value to add to key - public static void AddOrUpdate(this IDictionary dict, T key, int value) - where T : notnull + /// + /// Add a value by key to a dictionary, if the key doesn't exist, create it + /// + /// Dictionary key type + /// Dictionary to add/update + /// Key to update by + /// Value to add to key + public static void AddOrUpdate(this IDictionary dict, T key, int value) + where T : notnull + { + if (!dict.TryAdd(key, value)) { - if (!dict.TryAdd(key, value)) - { - dict[key] += value; - } + dict[key] += value; } + } - public static void RemoveItems(this IDictionary collection, ISet idsToRemove) + public static void RemoveItems(this IDictionary collection, ISet idsToRemove) + { + foreach (var key in idsToRemove) { - foreach (var key in idsToRemove) - { - collection.Remove(key); - } + collection.Remove(key); } } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/EndRaidResultExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/EndRaidResultExtensions.cs index 53104a8f..213fde1e 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/EndRaidResultExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/EndRaidResultExtensions.cs @@ -1,73 +1,72 @@ using SPTarkov.Server.Core.Models.Eft.Match; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class EndRaidResultExtensions { - public static class EndRaidResultExtensions + private static readonly HashSet _deathStates = [ExitStatus.KILLED, ExitStatus.MISSINGINACTION, ExitStatus.LEFT]; + + /// + /// Checks to see if player survives. run through will return false + /// + /// Post raid request + /// True if survived + public static bool IsPlayerSurvived(this EndRaidResult results) { - private static readonly HashSet _deathStates = [ExitStatus.KILLED, ExitStatus.MISSINGINACTION, ExitStatus.LEFT]; + return results.Result == ExitStatus.SURVIVED; + } - /// - /// Checks to see if player survives. run through will return false - /// - /// Post raid request - /// True if survived - public static bool IsPlayerSurvived(this EndRaidResult results) + /// + /// Is the player dead after a raid - dead = anything other than "survived" / "runner" + /// + /// Post raid request + /// True if dead + public static bool IsPlayerDead(this EndRaidResult results) + { + return _deathStates.Contains(results.Result.Value); + } + + /// + /// Has the player moved from one map to another + /// + /// Post raid request + /// True if players transferred + public static bool IsMapToMapTransfer(this EndRaidResult results) + { + return results.Result == ExitStatus.TRANSIT; + } + + /// + /// Was extract by car + /// + /// Result object from completed raid + /// Car extract names + /// True if extract was by car + public static bool TookCarExtract(this EndRaidResult? requestResults, HashSet carExtracts) + { + // exit name is undefined on death + if (string.IsNullOrEmpty(requestResults?.ExitName)) { - return results.Result == ExitStatus.SURVIVED; + return false; } - /// - /// Is the player dead after a raid - dead = anything other than "survived" / "runner" - /// - /// Post raid request - /// True if dead - public static bool IsPlayerDead(this EndRaidResult results) + if (requestResults.ExitName.ToLowerInvariant().Contains("v-ex")) { - return _deathStates.Contains(results.Result.Value); + return true; } - /// - /// Has the player moved from one map to another - /// - /// Post raid request - /// True if players transferred - public static bool IsMapToMapTransfer(this EndRaidResult results) - { - return results.Result == ExitStatus.TRANSIT; - } + return carExtracts.Contains(requestResults.ExitName.Trim()); + } - /// - /// Was extract by car - /// - /// Result object from completed raid - /// Car extract names - /// True if extract was by car - public static bool TookCarExtract(this EndRaidResult? requestResults, HashSet carExtracts) - { - // exit name is undefined on death - if (string.IsNullOrEmpty(requestResults?.ExitName)) - { - return false; - } - - if (requestResults.ExitName.ToLowerInvariant().Contains("v-ex")) - { - return true; - } - - return carExtracts.Contains(requestResults.ExitName.Trim()); - } - - /// - /// Raid exit was via coop extract - /// - /// Result object from completed raid - /// - /// True when exit was coop extract - public static bool TookCoopExtract(this EndRaidResult? raidResult, HashSet coopExtracts) - { - return raidResult?.ExitName is not null && coopExtracts.Contains(raidResult.ExitName.Trim()); - } + /// + /// Raid exit was via coop extract + /// + /// Result object from completed raid + /// + /// True when exit was coop extract + public static bool TookCoopExtract(this EndRaidResult? raidResult, HashSet coopExtracts) + { + return raidResult?.ExitName is not null && coopExtracts.Contains(raidResult.ExitName.Trim()); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/FullProfileExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/FullProfileExtensions.cs index 3a3682da..d5786557 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/FullProfileExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/FullProfileExtensions.cs @@ -3,221 +3,220 @@ using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions -{ - public static class FullProfileExtensions - { - /// - /// Add a list of suit ids to a profiles suit list, no duplicates - /// - /// Profile to add clothing to - /// Clothing Ids to add to profile - public static void AddSuitsToProfile(this SptProfile fullProfile, IEnumerable clothingIds) - { - fullProfile.CustomisationUnlocks ??= []; +namespace SPTarkov.Server.Core.Extensions; - foreach (var suitId in clothingIds) +public static class FullProfileExtensions +{ + /// + /// Add a list of suit ids to a profiles suit list, no duplicates + /// + /// Profile to add clothing to + /// Clothing Ids to add to profile + public static void AddSuitsToProfile(this SptProfile fullProfile, IEnumerable clothingIds) + { + fullProfile.CustomisationUnlocks ??= []; + + foreach (var suitId in clothingIds) + { + if (!fullProfile.CustomisationUnlocks.Exists(customisation => customisation.Id == suitId)) { - if (!fullProfile.CustomisationUnlocks.Exists(customisation => customisation.Id == suitId)) - { - // Clothing item doesn't exist in profile, add it - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = suitId, - Source = CustomisationSource.UNLOCKED_IN_GAME, - Type = CustomisationType.SUITE, - } - ); - } + // Clothing item doesn't exist in profile, add it + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = suitId, + Source = CustomisationSource.UNLOCKED_IN_GAME, + Type = CustomisationType.SUITE, + } + ); } } + } - /// - /// Add customisations to game profiles based on game edition - /// - /// Profile to add customisations to - public static void AddCustomisationUnlocksToProfile(this SptProfile fullProfile) + /// + /// Add customisations to game profiles based on game edition + /// + /// Profile to add customisations to + public static void AddCustomisationUnlocksToProfile(this SptProfile fullProfile) + { + // Some game versions have additional customisation unlocks + var gameEdition = fullProfile.GetGameEdition(); + + switch (gameEdition) { - // Some game versions have additional customisation unlocks - var gameEdition = fullProfile.GetGameEdition(); + case GameEditions.EDGE_OF_DARKNESS: + // Gets EoD tags + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "6746fd09bafff85008048838", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - switch (gameEdition) - { - case GameEditions.EDGE_OF_DARKNESS: - // Gets EoD tags - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "6746fd09bafff85008048838", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "67471938bafff850080488b7", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "67471938bafff850080488b7", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + break; + case GameEditions.UNHEARD: + // Gets EoD+Unheard tags + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "6746fd09bafff85008048838", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - break; - case GameEditions.UNHEARD: - // Gets EoD+Unheard tags - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "6746fd09bafff85008048838", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "67471938bafff850080488b7", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "67471938bafff850080488b7", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "67471928d17d6431550563b5", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "67471928d17d6431550563b5", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "6747193f170146228c0d2226", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, + } + ); - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "6747193f170146228c0d2226", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); + // Unheard Clothing (Cultist Hood) + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "666841a02537107dc508b704", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.SUITE, + } + ); - // Unheard Clothing (Cultist Hood) - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "666841a02537107dc508b704", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.SUITE, - } - ); + // Unheard background + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "675850ba33627edb710b0592", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.ENVIRONMENT, + } + ); - // Unheard background - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "675850ba33627edb710b0592", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.ENVIRONMENT, - } - ); + break; + } - break; - } + var prestigeLevel = fullProfile?.CharacterData?.PmcData?.Info?.PrestigeLevel; - var prestigeLevel = fullProfile?.CharacterData?.PmcData?.Info?.PrestigeLevel; - - if (prestigeLevel is not null) - { - if (prestigeLevel >= 1) - { - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "674dbf593bee1152d407f005", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); - } - - if (prestigeLevel >= 2) - { - fullProfile.CustomisationUnlocks.Add( - new CustomisationStorage - { - Id = "675dcfea7ae1a8792107ca99", - Source = CustomisationSource.DEFAULT, - Type = CustomisationType.DOG_TAG, - } - ); - } - } - - // Dev profile additions - if (fullProfile.ProfileInfo.Edition.ToLowerInvariant().Contains("developer")) - // CyberTark background + if (prestigeLevel is not null) + { + if (prestigeLevel >= 1) { fullProfile.CustomisationUnlocks.Add( new CustomisationStorage { - Id = "67585108def253bd97084552", + Id = "674dbf593bee1152d407f005", Source = CustomisationSource.DEFAULT, - Type = CustomisationType.ENVIRONMENT, + Type = CustomisationType.DOG_TAG, + } + ); + } + + if (prestigeLevel >= 2) + { + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "675dcfea7ae1a8792107ca99", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.DOG_TAG, } ); } } - /// - /// Get the game edition of a profile chosen on creation in Launcher - /// - public static string GetGameEdition(this SptProfile fullProfile) + // Dev profile additions + if (fullProfile.ProfileInfo.Edition.ToLowerInvariant().Contains("developer")) + // CyberTark background { - var edition = fullProfile.CharacterData?.PmcData?.Info?.GameVersion; - if (edition is not null) - { - return edition; - } - - // Edge case - profile not created yet, fall back to what launcher has set - var launcherEdition = fullProfile.ProfileInfo.Edition; - switch (launcherEdition.ToLowerInvariant()) - { - case "edge of darkness": - return GameEditions.EDGE_OF_DARKNESS; - case "unheard": - return GameEditions.UNHEARD; - default: - return GameEditions.STANDARD; - } - } - - /// - /// Add the given number of extra repeatable quests for the given type of repeatable to the users profile - /// - /// Profile to add the extra repeatable to - /// The ID of the type of repeatable to increase - /// The number of extra repeatables to add - public static void AddExtraRepeatableQuest(this SptProfile fullProfile, MongoId repeatableId, double rewardValue) - { - fullProfile.SptData.ExtraRepeatableQuests ??= new Dictionary(); - - if (!fullProfile.SptData.ExtraRepeatableQuests.TryAdd(repeatableId, 0)) - { - fullProfile.SptData.ExtraRepeatableQuests[repeatableId] += rewardValue; - } - } - - /// - /// Is the provided session id for a developer account - /// - /// Profile to check - /// True if account is developer - public static bool IsDeveloperAccount(this SptProfile fullProfile) - { - return fullProfile?.ProfileInfo?.Edition?.ToLowerInvariant().StartsWith("spt developer") ?? false; + fullProfile.CustomisationUnlocks.Add( + new CustomisationStorage + { + Id = "67585108def253bd97084552", + Source = CustomisationSource.DEFAULT, + Type = CustomisationType.ENVIRONMENT, + } + ); } } + + /// + /// Get the game edition of a profile chosen on creation in Launcher + /// + public static string GetGameEdition(this SptProfile fullProfile) + { + var edition = fullProfile.CharacterData?.PmcData?.Info?.GameVersion; + if (edition is not null) + { + return edition; + } + + // Edge case - profile not created yet, fall back to what launcher has set + var launcherEdition = fullProfile.ProfileInfo.Edition; + switch (launcherEdition.ToLowerInvariant()) + { + case "edge of darkness": + return GameEditions.EDGE_OF_DARKNESS; + case "unheard": + return GameEditions.UNHEARD; + default: + return GameEditions.STANDARD; + } + } + + /// + /// Add the given number of extra repeatable quests for the given type of repeatable to the users profile + /// + /// Profile to add the extra repeatable to + /// The ID of the type of repeatable to increase + /// The number of extra repeatables to add + public static void AddExtraRepeatableQuest(this SptProfile fullProfile, MongoId repeatableId, double rewardValue) + { + fullProfile.SptData.ExtraRepeatableQuests ??= new Dictionary(); + + if (!fullProfile.SptData.ExtraRepeatableQuests.TryAdd(repeatableId, 0)) + { + fullProfile.SptData.ExtraRepeatableQuests[repeatableId] += rewardValue; + } + } + + /// + /// Is the provided session id for a developer account + /// + /// Profile to check + /// True if account is developer + public static bool IsDeveloperAccount(this SptProfile fullProfile) + { + return fullProfile?.ProfileInfo?.Edition?.ToLowerInvariant().StartsWith("spt developer") ?? false; + } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemEventRouterResponseExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemEventRouterResponseExtensions.cs index fb1dc1ce..f3c51bb0 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemEventRouterResponseExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemEventRouterResponseExtensions.cs @@ -2,32 +2,31 @@ using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.ItemEvent; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class ItemEventRouterResponseExtensions { - public static class ItemEventRouterResponseExtensions + /// + /// Add item stack change object into output route event response + /// + /// Response to add item change event into + /// Session id + /// Item that was adjusted + public static void AddItemStackSizeChangeIntoEventResponse(this ItemEventRouterResponse output, MongoId sessionId, Item item) { - /// - /// Add item stack change object into output route event response - /// - /// Response to add item change event into - /// Session id - /// Item that was adjusted - public static void AddItemStackSizeChangeIntoEventResponse(this ItemEventRouterResponse output, MongoId sessionId, Item item) - { - // TODO: replace with something safer like TryGet - output - .ProfileChanges[sessionId] - .Items.ChangedItems.Add( - new Item - { - Id = item.Id, - Template = item.Template, - ParentId = item.ParentId, - SlotId = item.SlotId, - Location = item.Location, - Upd = new Upd { StackObjectsCount = item.Upd.StackObjectsCount }, - } - ); - } + // TODO: replace with something safer like TryGet + output + .ProfileChanges[sessionId] + .Items.ChangedItems.Add( + new Item + { + Id = item.Id, + Template = item.Template, + ParentId = item.ParentId, + SlotId = item.SlotId, + Location = item.Location, + Upd = new Upd { StackObjectsCount = item.Upd.StackObjectsCount }, + } + ); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs index a0ce4565..12d326c5 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs @@ -5,447 +5,446 @@ using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class ItemExtensions { - public static class ItemExtensions + /// + /// This method will compare two items and see if they are equivalent + /// This method will NOT compare IDs on the items + /// + /// first item to compare + /// second item to compare + /// Upd properties to compare between the items + /// true if they are the same + public static bool IsSameItem(this Item item1, Item item2, ISet? compareUpdProperties = null) { - /// - /// This method will compare two items and see if they are equivalent - /// This method will NOT compare IDs on the items - /// - /// first item to compare - /// second item to compare - /// Upd properties to compare between the items - /// true if they are the same - public static bool IsSameItem(this Item item1, Item item2, ISet? compareUpdProperties = null) + // Different tpl == different item + if (item1.Template != item2.Template) { - // Different tpl == different item - if (item1.Template != item2.Template) - { - return false; - } - - // Both lack upd object + same tpl = same - if (item1.Upd is null && item2.Upd is null) - { - return true; - } - - // item1 lacks upd, item2 has one - if (item1.Upd is null && item2.Upd is not null) - { - return false; - } - - // item1 has upd, item2 lacks one - if (item1.Upd is not null && item2.Upd is null) - { - return false; - } - - // key = Upd property Type as string, value = comparison function that returns bool - var comparers = new Dictionary> - { - { "Key", (upd1, upd2) => upd1.Key?.NumberOfUsages == upd2.Key?.NumberOfUsages }, - { "Buff", (upd1, upd2) => upd1.Buff?.Value == upd2.Buff?.Value && upd1.Buff?.BuffType == upd2.Buff?.BuffType }, - { "CultistAmulet", (upd1, upd2) => upd1.CultistAmulet?.NumberOfUsages == upd2.CultistAmulet?.NumberOfUsages }, - { "Dogtag", (upd1, upd2) => upd1.Dogtag?.ProfileId == upd2.Dogtag?.ProfileId }, - { "FaceShield", (upd1, upd2) => upd1.FaceShield?.Hits == upd2.FaceShield?.Hits }, - { - "Foldable", - (upd1, upd2) => upd1.Foldable?.Folded.GetValueOrDefault(false) == upd2.Foldable?.Folded.GetValueOrDefault(false) - }, - { "FoodDrink", (upd1, upd2) => upd1.FoodDrink?.HpPercent == upd2.FoodDrink?.HpPercent }, - { "MedKit", (upd1, upd2) => upd1.MedKit?.HpResource == upd2.MedKit?.HpResource }, - { "RecodableComponent", (upd1, upd2) => upd1.RecodableComponent?.IsEncoded == upd2.RecodableComponent?.IsEncoded }, - { "RepairKit", (upd1, upd2) => upd1.RepairKit?.Resource == upd2.RepairKit?.Resource }, - { "Resource", (upd1, upd2) => upd1.Resource?.UnitsConsumed == upd2.Resource?.UnitsConsumed }, - }; - - // Choose above keys or passed in keys to compare items with - var valuesToCompare = compareUpdProperties?.Count > 0 ? compareUpdProperties : comparers.Keys.ToHashSet(); - foreach (var propertyName in valuesToCompare) - { - if (!comparers.TryGetValue(propertyName, out var comparer)) - // Key not found, skip - { - continue; - } - - if (!comparer(item1.Upd, item2.Upd)) - { - return false; - } - } + return false; + } + // Both lack upd object + same tpl = same + if (item1.Upd is null && item2.Upd is null) + { return true; } - /// - /// Check if item is stored inside a container - /// - /// Item to check is inside of container - /// Name of slot to check item is in e.g. SecuredContainer/Backpack - /// Inventory with child parent items to check - /// True when item is in container - public static bool ItemIsInsideContainer(this Item itemToCheck, string desiredContainerSlotId, IEnumerable items) + // item1 lacks upd, item2 has one + if (item1.Upd is null && item2.Upd is not null) { - // Get items parent - var parent = items.FirstOrDefault(item => item.Id.Equals(itemToCheck.ParentId)); - if (parent is null) - // No parent, end of line, not inside container + return false; + } + + // item1 has upd, item2 lacks one + if (item1.Upd is not null && item2.Upd is null) + { + return false; + } + + // key = Upd property Type as string, value = comparison function that returns bool + var comparers = new Dictionary> + { + { "Key", (upd1, upd2) => upd1.Key?.NumberOfUsages == upd2.Key?.NumberOfUsages }, + { "Buff", (upd1, upd2) => upd1.Buff?.Value == upd2.Buff?.Value && upd1.Buff?.BuffType == upd2.Buff?.BuffType }, + { "CultistAmulet", (upd1, upd2) => upd1.CultistAmulet?.NumberOfUsages == upd2.CultistAmulet?.NumberOfUsages }, + { "Dogtag", (upd1, upd2) => upd1.Dogtag?.ProfileId == upd2.Dogtag?.ProfileId }, + { "FaceShield", (upd1, upd2) => upd1.FaceShield?.Hits == upd2.FaceShield?.Hits }, + { + "Foldable", + (upd1, upd2) => upd1.Foldable?.Folded.GetValueOrDefault(false) == upd2.Foldable?.Folded.GetValueOrDefault(false) + }, + { "FoodDrink", (upd1, upd2) => upd1.FoodDrink?.HpPercent == upd2.FoodDrink?.HpPercent }, + { "MedKit", (upd1, upd2) => upd1.MedKit?.HpResource == upd2.MedKit?.HpResource }, + { "RecodableComponent", (upd1, upd2) => upd1.RecodableComponent?.IsEncoded == upd2.RecodableComponent?.IsEncoded }, + { "RepairKit", (upd1, upd2) => upd1.RepairKit?.Resource == upd2.RepairKit?.Resource }, + { "Resource", (upd1, upd2) => upd1.Resource?.UnitsConsumed == upd2.Resource?.UnitsConsumed }, + }; + + // Choose above keys or passed in keys to compare items with + var valuesToCompare = compareUpdProperties?.Count > 0 ? compareUpdProperties : comparers.Keys.ToHashSet(); + foreach (var propertyName in valuesToCompare) + { + if (!comparers.TryGetValue(propertyName, out var comparer)) + // Key not found, skip + { + continue; + } + + if (!comparer(item1.Upd, item2.Upd)) { return false; } + } - if (parent.SlotId == desiredContainerSlotId) + return true; + } + + /// + /// Check if item is stored inside a container + /// + /// Item to check is inside of container + /// Name of slot to check item is in e.g. SecuredContainer/Backpack + /// Inventory with child parent items to check + /// True when item is in container + public static bool ItemIsInsideContainer(this Item itemToCheck, string desiredContainerSlotId, IEnumerable items) + { + // Get items parent + var parent = items.FirstOrDefault(item => item.Id.Equals(itemToCheck.ParentId)); + if (parent is null) + // No parent, end of line, not inside container + { + return false; + } + + if (parent.SlotId == desiredContainerSlotId) + { + return true; + } + + return parent.ItemIsInsideContainer(desiredContainerSlotId, items); + } + + /// + /// Get the size of a stack, return 1 if no stack object count property found + /// + /// Item to get stack size of + /// size of stack + public static int GetItemStackSize(this Item item) + { + if (item.Upd?.StackObjectsCount is not null) + { + return (int)item.Upd.StackObjectsCount; + } + + return 1; + } + + /// + /// Create a dictionary from a collection of items, keyed by item id + /// + /// Collection of items + /// Dictionary of items + public static Dictionary GenerateItemsMap(this IEnumerable items) + { + // Convert list to dictionary, keyed by items Id + return items.ToDictionary(item => item.Id); + } + + /// + /// Adopts orphaned items by resetting them as root "hideout" items. Helpful in situations where a parent has been + /// deleted from a group of items and there are children still referencing the missing parent. This method will + /// remove the reference from the children to the parent and set item properties to root values. + /// + /// The ID of the "root" of the container + /// Array of Items that should be adjusted + /// Returns Array of Items that have been adopted + public static List AdoptOrphanedItems(this List items, string rootId) + { + foreach (var item in items) + { + // Check if the item's parent exists. + var parentExists = items.Any(parentItem => parentItem.Id.Equals(item.ParentId)); + + // If the parent does not exist and the item is not already a 'hideout' item, adopt the orphaned item by + // setting the parent ID to the PMCs inventory equipment ID, the slot ID to 'hideout', and remove the location. + if (!parentExists && item.ParentId != rootId && item.SlotId != "hideout") { - return true; + item.ParentId = rootId; + item.SlotId = "hideout"; + item.Location = null; + } + } + + return items; + } + + /// + /// Recursive function that looks at every item from parameter and gets their children's Ids + includes parent item in results + /// + /// List of items (item + possible children) + /// Parent item's id + /// list of child item ids + public static List GetItemWithChildrenTpls(this IEnumerable items, MongoId baseItemId) + { + List list = []; + + foreach (var childItem in items) + { + if (childItem.ParentId == baseItemId.ToString()) + { + list.AddRange(GetItemWithChildrenTpls(items, childItem.Id)); + } + } + + list.Add(baseItemId); // Required, push original item id onto array + + return list; + } + + /// + /// Check if the passed in item has buy count restrictions + /// + /// Item to check + /// true if it has buy restrictions + public static bool HasBuyRestrictions(this Item itemToCheck) + { + return itemToCheck.Upd?.BuyRestrictionCurrent is not null && itemToCheck.Upd?.BuyRestrictionMax is not null; + } + + /// + /// Gets the identifier for a child using slotId, locationX and locationY. + /// + /// Item. + /// SlotId OR slotId, locationX, locationY. + public static string GetChildId(this Item item) + { + if (item.Location is null) + { + return item.SlotId; + } + + var LocationTyped = (ItemLocation)item.Location; + + return $"{item.SlotId},{LocationTyped.X},{LocationTyped.Y}"; + } + + public static bool IsVertical(this ItemLocation itemLocation) + { + return itemLocation.R == ItemRotation.Vertical; + } + + /// + /// Update items upd.StackObjectsCount to be 1 if its upd is missing or StackObjectsCount is undefined + /// + /// Item to update + /// Fixed item + public static void FixItemStackCount(this Item item) + { + // Ensure item has 'Upd' object + item.Upd ??= new Upd { StackObjectsCount = 1 }; + + // Ensure item has 'StackObjectsCount' property + item.Upd.StackObjectsCount ??= 1; + } + + /// + /// Get an item with its attachments (children) + /// + /// List of items (item + possible children) + /// Parent item's id + /// OPTIONAL - Include only mod items, exclude items stored inside root item + /// list of Item objects + public static List GetItemWithChildren(this IEnumerable items, MongoId baseItemId, bool excludeStoredItems = false) + { + // Use dictionary to make key lookup faster, convert to list before being returned + var itemList = items.ToList(); + OrderedDictionary result = []; + + // Find desired root item + var desiredRootItem = itemList.FirstOrDefault(item => item.Id == baseItemId); + if (desiredRootItem is null) + { + // Root not found, nothing to return, exit + return []; + } + result.Add(desiredRootItem.Id, desiredRootItem); + var rootItemIdString = desiredRootItem.Id.ToString(); + + foreach (var item in itemList) + { + if (result.ContainsKey(item.Id)) + { + // Already processed, skip + continue; } - return parent.ItemIsInsideContainer(desiredContainerSlotId, items); - } - - /// - /// Get the size of a stack, return 1 if no stack object count property found - /// - /// Item to get stack size of - /// size of stack - public static int GetItemStackSize(this Item item) - { - if (item.Upd?.StackObjectsCount is not null) + // Skip items with different parentId + if (item.ParentId != rootItemIdString) { - return (int)item.Upd.StackObjectsCount; + continue; } - return 1; - } - - /// - /// Create a dictionary from a collection of items, keyed by item id - /// - /// Collection of items - /// Dictionary of items - public static Dictionary GenerateItemsMap(this IEnumerable items) - { - // Convert list to dictionary, keyed by items Id - return items.ToDictionary(item => item.Id); - } - - /// - /// Adopts orphaned items by resetting them as root "hideout" items. Helpful in situations where a parent has been - /// deleted from a group of items and there are children still referencing the missing parent. This method will - /// remove the reference from the children to the parent and set item properties to root values. - /// - /// The ID of the "root" of the container - /// Array of Items that should be adjusted - /// Returns Array of Items that have been adopted - public static List AdoptOrphanedItems(this List items, string rootId) - { - foreach (var item in items) + // Is stored in parent and disallowed + if (excludeStoredItems && item.Location is not null) { - // Check if the item's parent exists. - var parentExists = items.Any(parentItem => parentItem.Id.Equals(item.ParentId)); + continue; + } - // If the parent does not exist and the item is not already a 'hideout' item, adopt the orphaned item by - // setting the parent ID to the PMCs inventory equipment ID, the slot ID to 'hideout', and remove the location. - if (!parentExists && item.ParentId != rootId && item.SlotId != "hideout") + // Item may have children, check + foreach (var subItem in GetItemWithChildren(itemList, item.Id)) + { + result.Add(subItem.Id, subItem); + } + } + + return result.Values.ToList(); + } + + /// + /// Convert an Item to SptLootItem + /// + /// Item to convert + /// Converted SptLootItem + public static SptLootItem ToLootItem(this Item item) + { + return new SptLootItem + { + ComposedKey = null, + Id = item.Id, + Template = item.Template, + Upd = item.Upd, + ParentId = item.ParentId, + SlotId = item.SlotId, + Location = item.Location, + Desc = item.Desc, + ExtensionData = item.ExtensionData, + }; + } + + public static ItemLocation? GetParsedLocation(this Item item) + { + if (item.Location is null) + { + return null; + } + + if (item.Location is JsonElement element) + { + // TODO: when is this true + return element.ToObject(); + } + + return (ItemLocation)item.Location; + } + + /// + /// Get a list of the item IDs (NOT tpls) inside a secure container + /// + /// Inventory items to look for secure container in + /// List of ids + public static HashSet GetSecureContainerItems(this IEnumerable items) + { + var secureContainer = items.First(x => x.SlotId == "SecuredContainer"); + + // No container found, drop out + if (secureContainer is null) + { + return []; + } + + var itemsInSecureContainer = items.GetItemWithChildrenTpls(secureContainer.Id); + + // Return all items returned and exclude the secure container item itself + return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToHashSet(); + } + + /// + /// Regenerate all GUIDs with new IDs, except special item types (e.g. quest, sorting table, etc.) + /// + /// + /// + public static IEnumerable ReplaceIDs(this IEnumerable items) + { + foreach (var item in items) + { + // Generate new id + var newId = new MongoId(); + + // Keep copy of original id + var originalId = item.Id; + + // Update items id to new one we generated + item.Id = newId; + + // Find all children of item and update their parent ids to match + var childItems = items.Where(item => item.ParentId == originalId.ToString()); + foreach (var childItem in childItems) + { + childItem.ParentId = newId; + } + } + + return items; + } + + /// + /// Update a root items _id property value to be unique + /// + /// Item to update root items _id property + /// Optional: new id to use + /// New root id + public static MongoId RemapRootItemId(this IEnumerable itemWithChildren, MongoId? newId = null) + { + newId ??= new MongoId(); + + var rootItemExistingId = itemWithChildren.FirstOrDefault().Id; + + foreach (var item in itemWithChildren) + { + // Root, update id + if (item.Id.Equals(rootItemExistingId)) + { + item.Id = newId.Value; + + continue; + } + + // Child with parent of root, update + if (item.ParentId == rootItemExistingId) + { + item.ParentId = newId.Value; + } + } + + return newId.Value; + } + + /// + /// Create hashsets for passed in items, keyed by the items ID and by the items parentId + /// + /// Items to hash + /// InventoryItemHash + public static InventoryItemHash GetInventoryItemHash(this IEnumerable inventoryItems) + { + // Group by parentId + turn value into mongoId as we've filtered out non-mongoId values + var byParentId = inventoryItems + .Where(item => !string.IsNullOrEmpty(item.ParentId) && item.ParentId != "hideout") + .GroupBy(item => new MongoId(item.ParentId)) + .ToDictionary(kvp => kvp.Key, group => group.ToHashSet()); + + return new InventoryItemHash { ByItemId = inventoryItems.ToDictionary(item => item.Id), ByParentId = byParentId }; + } + + /// + /// Remove spawned in session (FiR) status from items inside a container + /// + /// Player profile + /// Container slot id to find items for and remove FiR from e.g. "Backpack" + public static void RemoveFiRStatusFromItemsInContainer(this PmcData pmcData, string containerSlotId) + { + var container = pmcData?.Inventory?.Items?.FirstOrDefault(item => item.SlotId == containerSlotId); + if (container is null) + { + return; + } + + var parentItemLookup = pmcData.Inventory.Items.ToLookup(item => item.ParentId); + var parentIdsToSearch = new Queue(); + parentIdsToSearch.Enqueue(container.Id); + + while (parentIdsToSearch.Count > 0) + { + var currentParentId = parentIdsToSearch.Dequeue(); + foreach (var childItem in parentItemLookup[currentParentId]) + { + if (childItem.Upd?.SpawnedInSession != null && childItem.Upd.SpawnedInSession.Value) { - item.ParentId = rootId; - item.SlotId = "hideout"; - item.Location = null; - } - } - - return items; - } - - /// - /// Recursive function that looks at every item from parameter and gets their children's Ids + includes parent item in results - /// - /// List of items (item + possible children) - /// Parent item's id - /// list of child item ids - public static List GetItemWithChildrenTpls(this IEnumerable items, MongoId baseItemId) - { - List list = []; - - foreach (var childItem in items) - { - if (childItem.ParentId == baseItemId.ToString()) - { - list.AddRange(GetItemWithChildrenTpls(items, childItem.Id)); - } - } - - list.Add(baseItemId); // Required, push original item id onto array - - return list; - } - - /// - /// Check if the passed in item has buy count restrictions - /// - /// Item to check - /// true if it has buy restrictions - public static bool HasBuyRestrictions(this Item itemToCheck) - { - return itemToCheck.Upd?.BuyRestrictionCurrent is not null && itemToCheck.Upd?.BuyRestrictionMax is not null; - } - - /// - /// Gets the identifier for a child using slotId, locationX and locationY. - /// - /// Item. - /// SlotId OR slotId, locationX, locationY. - public static string GetChildId(this Item item) - { - if (item.Location is null) - { - return item.SlotId; - } - - var LocationTyped = (ItemLocation)item.Location; - - return $"{item.SlotId},{LocationTyped.X},{LocationTyped.Y}"; - } - - public static bool IsVertical(this ItemLocation itemLocation) - { - return itemLocation.R == ItemRotation.Vertical; - } - - /// - /// Update items upd.StackObjectsCount to be 1 if its upd is missing or StackObjectsCount is undefined - /// - /// Item to update - /// Fixed item - public static void FixItemStackCount(this Item item) - { - // Ensure item has 'Upd' object - item.Upd ??= new Upd { StackObjectsCount = 1 }; - - // Ensure item has 'StackObjectsCount' property - item.Upd.StackObjectsCount ??= 1; - } - - /// - /// Get an item with its attachments (children) - /// - /// List of items (item + possible children) - /// Parent item's id - /// OPTIONAL - Include only mod items, exclude items stored inside root item - /// list of Item objects - public static List GetItemWithChildren(this IEnumerable items, MongoId baseItemId, bool excludeStoredItems = false) - { - // Use dictionary to make key lookup faster, convert to list before being returned - var itemList = items.ToList(); - OrderedDictionary result = []; - - // Find desired root item - var desiredRootItem = itemList.FirstOrDefault(item => item.Id == baseItemId); - if (desiredRootItem is null) - { - // Root not found, nothing to return, exit - return []; - } - result.Add(desiredRootItem.Id, desiredRootItem); - var rootItemIdString = desiredRootItem.Id.ToString(); - - foreach (var item in itemList) - { - if (result.ContainsKey(item.Id)) - { - // Already processed, skip - continue; + childItem.Upd.SpawnedInSession = false; } - // Skip items with different parentId - if (item.ParentId != rootItemIdString) - { - continue; - } - - // Is stored in parent and disallowed - if (excludeStoredItems && item.Location is not null) - { - continue; - } - - // Item may have children, check - foreach (var subItem in GetItemWithChildren(itemList, item.Id)) - { - result.Add(subItem.Id, subItem); - } - } - - return result.Values.ToList(); - } - - /// - /// Convert an Item to SptLootItem - /// - /// Item to convert - /// Converted SptLootItem - public static SptLootItem ToLootItem(this Item item) - { - return new SptLootItem - { - ComposedKey = null, - Id = item.Id, - Template = item.Template, - Upd = item.Upd, - ParentId = item.ParentId, - SlotId = item.SlotId, - Location = item.Location, - Desc = item.Desc, - ExtensionData = item.ExtensionData, - }; - } - - public static ItemLocation? GetParsedLocation(this Item item) - { - if (item.Location is null) - { - return null; - } - - if (item.Location is JsonElement element) - { - // TODO: when is this true - return element.ToObject(); - } - - return (ItemLocation)item.Location; - } - - /// - /// Get a list of the item IDs (NOT tpls) inside a secure container - /// - /// Inventory items to look for secure container in - /// List of ids - public static HashSet GetSecureContainerItems(this IEnumerable items) - { - var secureContainer = items.First(x => x.SlotId == "SecuredContainer"); - - // No container found, drop out - if (secureContainer is null) - { - return []; - } - - var itemsInSecureContainer = items.GetItemWithChildrenTpls(secureContainer.Id); - - // Return all items returned and exclude the secure container item itself - return itemsInSecureContainer.Where(x => x != secureContainer.Id).ToHashSet(); - } - - /// - /// Regenerate all GUIDs with new IDs, except special item types (e.g. quest, sorting table, etc.) - /// - /// - /// - public static IEnumerable ReplaceIDs(this IEnumerable items) - { - foreach (var item in items) - { - // Generate new id - var newId = new MongoId(); - - // Keep copy of original id - var originalId = item.Id; - - // Update items id to new one we generated - item.Id = newId; - - // Find all children of item and update their parent ids to match - var childItems = items.Where(item => item.ParentId == originalId.ToString()); - foreach (var childItem in childItems) - { - childItem.ParentId = newId; - } - } - - return items; - } - - /// - /// Update a root items _id property value to be unique - /// - /// Item to update root items _id property - /// Optional: new id to use - /// New root id - public static MongoId RemapRootItemId(this IEnumerable itemWithChildren, MongoId? newId = null) - { - newId ??= new MongoId(); - - var rootItemExistingId = itemWithChildren.FirstOrDefault().Id; - - foreach (var item in itemWithChildren) - { - // Root, update id - if (item.Id.Equals(rootItemExistingId)) - { - item.Id = newId.Value; - - continue; - } - - // Child with parent of root, update - if (item.ParentId == rootItemExistingId) - { - item.ParentId = newId.Value; - } - } - - return newId.Value; - } - - /// - /// Create hashsets for passed in items, keyed by the items ID and by the items parentId - /// - /// Items to hash - /// InventoryItemHash - public static InventoryItemHash GetInventoryItemHash(this IEnumerable inventoryItems) - { - // Group by parentId + turn value into mongoId as we've filtered out non-mongoId values - var byParentId = inventoryItems - .Where(item => !string.IsNullOrEmpty(item.ParentId) && item.ParentId != "hideout") - .GroupBy(item => new MongoId(item.ParentId)) - .ToDictionary(kvp => kvp.Key, group => group.ToHashSet()); - - return new InventoryItemHash { ByItemId = inventoryItems.ToDictionary(item => item.Id), ByParentId = byParentId }; - } - - /// - /// Remove spawned in session (FiR) status from items inside a container - /// - /// Player profile - /// Container slot id to find items for and remove FiR from e.g. "Backpack" - public static void RemoveFiRStatusFromItemsInContainer(this PmcData pmcData, string containerSlotId) - { - var container = pmcData?.Inventory?.Items?.FirstOrDefault(item => item.SlotId == containerSlotId); - if (container is null) - { - return; - } - - var parentItemLookup = pmcData.Inventory.Items.ToLookup(item => item.ParentId); - var parentIdsToSearch = new Queue(); - parentIdsToSearch.Enqueue(container.Id); - - while (parentIdsToSearch.Count > 0) - { - var currentParentId = parentIdsToSearch.Dequeue(); - foreach (var childItem in parentItemLookup[currentParentId]) - { - if (childItem.Upd?.SpawnedInSession != null && childItem.Upd.SpawnedInSession.Value) - { - childItem.Upd.SpawnedInSession = false; - } - - parentIdsToSearch.Enqueue(childItem.Id); - } + parentIdsToSearch.Enqueue(childItem.Id); } } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/LootContainerSettingsExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/LootContainerSettingsExtensions.cs index b365d6fd..ca5d007e 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/LootContainerSettingsExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/LootContainerSettingsExtensions.cs @@ -1,52 +1,51 @@ using SPTarkov.Server.Core.Models.Spt.Config; -namespace SPTarkov.Server.Core.Extensions -{ - /// - /// Get the rouble amount for the desired container, multiplied by the current map bot will spawn on - /// - public static class LootContainerSettingsExtensions - { - public static double GetRoubleValue(this LootContainerSettings settings, int botLevel, string? locationId) - { - var roubleTotalByLevel = GetContainerRoubleTotalByLevel(botLevel, settings.TotalRubByLevel); +namespace SPTarkov.Server.Core.Extensions; - if (locationId is null) +/// +/// Get the rouble amount for the desired container, multiplied by the current map bot will spawn on +/// +public static class LootContainerSettingsExtensions +{ + public static double GetRoubleValue(this LootContainerSettings settings, int botLevel, string? locationId) + { + var roubleTotalByLevel = GetContainerRoubleTotalByLevel(botLevel, settings.TotalRubByLevel); + + if (locationId is null) + { + return roubleTotalByLevel; + } + + // Get multiplier for map, use default if map not found + if (!settings.LocationMultiplier.TryGetValue(locationId, out var multiplier)) + { + if (!settings.LocationMultiplier.TryGetValue("default", out multiplier)) { return roubleTotalByLevel; } - - // Get multiplier for map, use default if map not found - if (!settings.LocationMultiplier.TryGetValue(locationId, out var multiplier)) - { - if (!settings.LocationMultiplier.TryGetValue("default", out multiplier)) - { - return roubleTotalByLevel; - } - } - - return roubleTotalByLevel * multiplier; } - /// - /// Gets the rouble cost total for loot in a bots backpack by the bots level - /// Will return 0 for non PMCs - /// - /// level of the bot - /// Pocket/vest/backpack - /// rouble amount - private static double GetContainerRoubleTotalByLevel(int botLevel, IEnumerable containerLootValuesPool) + return roubleTotalByLevel * multiplier; + } + + /// + /// Gets the rouble cost total for loot in a bots backpack by the bots level + /// Will return 0 for non PMCs + /// + /// level of the bot + /// Pocket/vest/backpack + /// rouble amount + private static double GetContainerRoubleTotalByLevel(int botLevel, IEnumerable containerLootValuesPool) + { + var matchingValue = containerLootValuesPool.FirstOrDefault(minMaxValue => + botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max + ); + + if (matchingValue is null) { - var matchingValue = containerLootValuesPool.FirstOrDefault(minMaxValue => - botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max - ); - - if (matchingValue is null) - { - return 1; - } - - return matchingValue.Value; + return 1; } + + return matchingValue.Value; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/MathExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/MathExtensions.cs index 7770f888..857f68fb 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/MathExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/MathExtensions.cs @@ -1,83 +1,82 @@ -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class MathExtensions { - public static class MathExtensions + /// + /// Helper to create the cumulative sum of all enumerable elements + /// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10] + /// + /// The enumerable with numbers of which to calculate the cumulative sum + /// cumulative sum of values + public static IEnumerable CumulativeSum(this IEnumerable values) { - /// - /// Helper to create the cumulative sum of all enumerable elements - /// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10] - /// - /// The enumerable with numbers of which to calculate the cumulative sum - /// cumulative sum of values - public static IEnumerable CumulativeSum(this IEnumerable values) + double sum = 0; + foreach (var value in values) { - double sum = 0; - foreach (var value in values) - { - sum += value; - yield return sum; - } - } - - /// - /// Helper to create the cumulative sum of all enumerable elements - /// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10] - /// - /// The enumerable with numbers of which to calculate the cumulative sum - /// cumulative sum of values - public static IEnumerable CumulativeSum(this IEnumerable values) - { - float sum = 0; - foreach (var value in values) - { - sum += value; - yield return sum; - } - } - - /// - /// Helper to create the product of each element times factor - /// - /// The enumerable of numbers which shall be multiplied by the factor - /// Number to multiply each element by - /// An enumerable of elements all multiplied by the factor - public static IEnumerable Product(this IEnumerable values, double factor) - { - return values.Select(v => v * factor); - } - - /// - /// Helper to create the product of each element times factor - /// - /// The enumerable of numbers which shall be multiplied by the factor - /// Number to multiply each element by - /// An enumerable of elements all multiplied by the factor - public static IEnumerable Product(this IEnumerable values, float factor) - { - return values.Select(v => v * factor); - } - - /// - /// Helper to determine if one double is approx equal to another double - /// - /// Value to check - /// Target value - /// Error value - /// True if value is approx target within the error range - public static bool Approx(this double value, double target, double error = 0.001d) - { - return Math.Abs(value - target) <= error; - } - - /// - /// Helper to determine if one float is approx equal to another float - /// - /// Value to check - /// Target value - /// Error value - /// True if value is approx target within the error range - public static bool Approx(this float value, float target, float error = 0.001f) - { - return Math.Abs(value - target) <= error; + sum += value; + yield return sum; } } + + /// + /// Helper to create the cumulative sum of all enumerable elements + /// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10] + /// + /// The enumerable with numbers of which to calculate the cumulative sum + /// cumulative sum of values + public static IEnumerable CumulativeSum(this IEnumerable values) + { + float sum = 0; + foreach (var value in values) + { + sum += value; + yield return sum; + } + } + + /// + /// Helper to create the product of each element times factor + /// + /// The enumerable of numbers which shall be multiplied by the factor + /// Number to multiply each element by + /// An enumerable of elements all multiplied by the factor + public static IEnumerable Product(this IEnumerable values, double factor) + { + return values.Select(v => v * factor); + } + + /// + /// Helper to create the product of each element times factor + /// + /// The enumerable of numbers which shall be multiplied by the factor + /// Number to multiply each element by + /// An enumerable of elements all multiplied by the factor + public static IEnumerable Product(this IEnumerable values, float factor) + { + return values.Select(v => v * factor); + } + + /// + /// Helper to determine if one double is approx equal to another double + /// + /// Value to check + /// Target value + /// Error value + /// True if value is approx target within the error range + public static bool Approx(this double value, double target, double error = 0.001d) + { + return Math.Abs(value - target) <= error; + } + + /// + /// Helper to determine if one float is approx equal to another float + /// + /// Value to check + /// Target value + /// Error value + /// True if value is approx target within the error range + public static bool Approx(this float value, float target, float error = 0.001f) + { + return Math.Abs(value - target) <= error; + } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/MongoIdExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/MongoIdExtensions.cs index b41db6b0..24e42b76 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/MongoIdExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/MongoIdExtensions.cs @@ -1,71 +1,70 @@ using SPTarkov.Server.Core.Models.Common; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class MongoIdExtensions { - public static class MongoIdExtensions + //Temporary, but necessary + public static IEnumerable ToMongoIds(this IEnumerable source) { - //Temporary, but necessary - public static IEnumerable ToMongoIds(this IEnumerable source) + return source.Select(s => (MongoId)s); + } + + /// + /// Determines whether the specified is a valid 24-character hexadecimal string, + /// which is the standard format for MongoDB ObjectIds. + /// + /// The to validate. + /// if the is a valid MongoDB ObjectId; otherwise, . + public static bool IsValidMongoId(this MongoId mongoId) + { + var span = mongoId.ToString().AsSpan(); + + if (span.Length != 24) { - return source.Select(s => (MongoId)s); + return false; } - /// - /// Determines whether the specified is a valid 24-character hexadecimal string, - /// which is the standard format for MongoDB ObjectIds. - /// - /// The to validate. - /// if the is a valid MongoDB ObjectId; otherwise, . - public static bool IsValidMongoId(this MongoId mongoId) + for (var i = 0; i < 24; i++) { - var span = mongoId.ToString().AsSpan(); + var c = span[i]; + var isHex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - if (span.Length != 24) + if (!isHex) { return false; } - - for (var i = 0; i < 24; i++) - { - var c = span[i]; - var isHex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - - if (!isHex) - { - return false; - } - } - - return true; } - /// - /// Determines whether the specified string is a valid 24-character hexadecimal representation - /// of a MongoDB ObjectId. - /// - /// The string to validate as a MongoDB ObjectId. - /// if the is a valid MongoDB ObjectId; otherwise, . - public static bool IsValidMongoId(this string mongoId) - { - var span = mongoId.AsSpan(); + return true; + } - if (span.Length != 24) + /// + /// Determines whether the specified string is a valid 24-character hexadecimal representation + /// of a MongoDB ObjectId. + /// + /// The string to validate as a MongoDB ObjectId. + /// if the is a valid MongoDB ObjectId; otherwise, . + public static bool IsValidMongoId(this string mongoId) + { + var span = mongoId.AsSpan(); + + if (span.Length != 24) + { + return false; + } + + for (var i = 0; i < 24; i++) + { + var c = span[i]; + var isHex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + + if (!isHex) { return false; } - - for (var i = 0; i < 24; i++) - { - var c = span[i]; - var isHex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - - if (!isHex) - { - return false; - } - } - - return true; } + + return true; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ProductionExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ProductionExtensions.cs index cdff0730..78300aeb 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ProductionExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ProductionExtensions.cs @@ -2,44 +2,43 @@ using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions -{ - public static class ProductionExtensions - { - /// - /// Has the craft completed - /// Ignores bitcoin farm/cultist circle as they're continuous crafts - /// - /// Craft to check - /// True when craft is complete - public static bool IsCraftComplete(this Production craft) - { - return craft.Progress >= craft.ProductionTime - && !craft.IsCraftOfType(HideoutAreas.BitcoinFarm) - && !craft.IsCraftOfType(HideoutAreas.CircleOfCultists); - } +namespace SPTarkov.Server.Core.Extensions; - /// - /// Is a craft from a particular hideout area - /// - /// Craft to check - /// Type to check craft against - /// True if it is from that area - public static bool IsCraftOfType(this Production craft, HideoutAreas hideoutType) +public static class ProductionExtensions +{ + /// + /// Has the craft completed + /// Ignores bitcoin farm/cultist circle as they're continuous crafts + /// + /// Craft to check + /// True when craft is complete + public static bool IsCraftComplete(this Production craft) + { + return craft.Progress >= craft.ProductionTime + && !craft.IsCraftOfType(HideoutAreas.BitcoinFarm) + && !craft.IsCraftOfType(HideoutAreas.CircleOfCultists); + } + + /// + /// Is a craft from a particular hideout area + /// + /// Craft to check + /// Type to check craft against + /// True if it is from that area + public static bool IsCraftOfType(this Production craft, HideoutAreas hideoutType) + { + switch (hideoutType) { - switch (hideoutType) - { - case HideoutAreas.WaterCollector: - return craft.RecipeId == HideoutHelper.WaterCollectorId; - case HideoutAreas.BitcoinFarm: - return craft.RecipeId == HideoutHelper.BitcoinProductionId; - case HideoutAreas.ScavCase: - return craft.SptIsScavCase ?? false; - case HideoutAreas.CircleOfCultists: - return craft.SptIsCultistCircle ?? false; - default: - return false; - } + case HideoutAreas.WaterCollector: + return craft.RecipeId == HideoutHelper.WaterCollectorId; + case HideoutAreas.BitcoinFarm: + return craft.RecipeId == HideoutHelper.BitcoinProductionId; + case HideoutAreas.ScavCase: + return craft.SptIsScavCase ?? false; + case HideoutAreas.CircleOfCultists: + return craft.SptIsCultistCircle ?? false; + default: + return false; } } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ProfileExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ProfileExtensions.cs index daad1bee..d8d1222d 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/ProfileExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/ProfileExtensions.cs @@ -3,241 +3,240 @@ using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class ProfileExtensions { - public static class ProfileExtensions + /// + /// Return all quest items current in the supplied profile + /// + /// Profile to get quest items from + /// List of item objects + public static IEnumerable GetQuestItemsInProfile(this PmcData profile) { - /// - /// Return all quest items current in the supplied profile - /// - /// Profile to get quest items from - /// List of item objects - public static IEnumerable GetQuestItemsInProfile(this PmcData profile) + return profile?.Inventory?.Items.Where(i => i.ParentId == profile.Inventory.QuestRaidItems).ToList(); + } + + /// + /// Upgrade hideout wall from starting level to interactable level if necessary stations have been upgraded + /// + /// Profile to upgrade wall in + public static void UnlockHideoutWallInProfile(this PmcData profile) + { + var profileHideoutAreas = profile.Hideout.Areas; + var waterCollector = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.WaterCollector); + var medStation = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.MedStation); + var wall = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.EmergencyWall); + + // No collector or med station, skip + if (waterCollector is null && medStation is null) { - return profile?.Inventory?.Items.Where(i => i.ParentId == profile.Inventory.QuestRaidItems).ToList(); + return; } - /// - /// Upgrade hideout wall from starting level to interactable level if necessary stations have been upgraded - /// - /// Profile to upgrade wall in - public static void UnlockHideoutWallInProfile(this PmcData profile) + // If med-station > level 1 AND water collector > level 1 AND wall is level 0 + if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0) { - var profileHideoutAreas = profile.Hideout.Areas; - var waterCollector = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.WaterCollector); - var medStation = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.MedStation); - var wall = profileHideoutAreas.FirstOrDefault(x => x.Type == HideoutAreas.EmergencyWall); + wall.Level = 3; + } + } - // No collector or med station, skip - if (waterCollector is null && medStation is null) + /// + /// Does the provided profile contain any condition counters + /// + /// Profile to check for condition counters + /// Profile has condition counters + public static bool ProfileHasConditionCounters(this PmcData profile) + { + if (profile.TaskConditionCounters is null) + { + return false; + } + + return profile.TaskConditionCounters.Count > 0; + } + + /// + /// Get a specific common skill from supplied profile + /// + /// Player profile + /// Skill to look up and return value from + /// Common skill object from desired profile + public static CommonSkill? GetSkillFromProfile(this PmcData profile, SkillTypes skill) + { + return profile?.Skills?.Common?.FirstOrDefault(s => s.Id == skill); + } + + /// + /// Get the scav karma level for a profile + /// Is also the fence trader rep level + /// + /// pmc profile + /// karma level + public static double GetScavKarmaLevel(this PmcData pmcData) + { + // can be empty during profile creation + if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo)) + { + return 0; + } + + if (fenceInfo.Standing > 6) + { + return 6; + } + + return Math.Floor(fenceInfo.Standing ?? 0); + } + + public static Skills GetSkillsOrDefault(this PmcData profile) + { + return profile?.Skills ?? GetDefaultSkills(); + } + + private static Skills GetDefaultSkills() + { + return new Skills + { + Common = [], + Mastering = [], + Points = 0, + }; + } + + /// + /// Recursively checks if the given item is + /// inside the stash, that is it has the stash as + /// ancestor with slotId=hideout + /// + /// Player profile + /// Item to look for + /// True if item exists inside stash + public static bool IsItemInStash(this PmcData pmcData, Item itemToCheck) + { + // Start recursive check + return pmcData.IsParentInStash(itemToCheck.Id); + } + + public static bool IsParentInStash(this PmcData pmcData, MongoId itemId) + { + // Item not found / has no parent + var item = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemId); + if (item?.ParentId is null) + { + return false; + } + + // Root level. Items parent is the stash with slotId "hideout" + if (item.ParentId == pmcData.Inventory.Stash && item.SlotId == "hideout") + { + return true; + } + + // Recursive case: Check the items parent + return IsParentInStash(pmcData, item.ParentId); + } + + /// + /// Iterate over all bonuses and sum up all bonuses of desired type in provided profile + /// + /// Player profile + /// Bonus to sum up + /// Summed bonus value or 0 if no bonus found + public static double GetBonusValueFromProfile(this PmcData pmcProfile, BonusType desiredBonus) + { + var bonuses = pmcProfile?.Bonuses?.Where(b => b.Type == desiredBonus); + if (!bonuses.Any()) + { + return 0; + } + + // Sum all bonuses found above + return bonuses?.Sum(bonus => bonus?.Value ?? 0) ?? 0; + } + + public static bool PlayerIsFleaBanned(this PmcData pmcProfile, long currentTimestamp) + { + return pmcProfile?.Info?.Bans?.Any(b => b.BanType == BanType.RagFair && currentTimestamp < b.DateTime) ?? false; + } + + /// + /// Calculates the current level of a player based on their accumulated experience points. + /// This method iterates through an experience table to determine the highest level achieved + /// by comparing the player's experience against cumulative thresholds. + /// + /// Player profile + /// Experience table from globals.json + /// + /// The calculated level of the player as an integer, or null if the level cannot be determined. + /// This value is also assigned to within the provided profile. + /// + public static int? CalculateLevel(this PmcData pmcData, ExpTable[] expTable) + { + var accExp = 0; + for (var i = 0; i < expTable.Length; i++) + { + accExp += expTable[i].Experience; + + if (pmcData.Info.Experience < accExp) { - return; + break; } - // If med-station > level 1 AND water collector > level 1 AND wall is level 0 - if (waterCollector?.Level >= 1 && medStation?.Level >= 1 && wall?.Level <= 0) - { - wall.Level = 3; - } + pmcData.Info.Level = i + 1; } - /// - /// Does the provided profile contain any condition counters - /// - /// Profile to check for condition counters - /// Profile has condition counters - public static bool ProfileHasConditionCounters(this PmcData profile) + return pmcData.Info.Level; + } + + /// + /// Does the provided item have a root item with the provided id + /// + /// Profile with items + /// Item to check + /// Root item id to check for + /// True when item has rootId, false when not + public static bool DoesItemHaveRootId(this PmcData pmcData, Item item, MongoId rootId) + { + var currentItem = item; + while (currentItem is not null) { - if (profile.TaskConditionCounters is null) - { - return false; - } - - return profile.TaskConditionCounters.Count > 0; - } - - /// - /// Get a specific common skill from supplied profile - /// - /// Player profile - /// Skill to look up and return value from - /// Common skill object from desired profile - public static CommonSkill? GetSkillFromProfile(this PmcData profile, SkillTypes skill) - { - return profile?.Skills?.Common?.FirstOrDefault(s => s.Id == skill); - } - - /// - /// Get the scav karma level for a profile - /// Is also the fence trader rep level - /// - /// pmc profile - /// karma level - public static double GetScavKarmaLevel(this PmcData pmcData) - { - // can be empty during profile creation - if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo)) - { - return 0; - } - - if (fenceInfo.Standing > 6) - { - return 6; - } - - return Math.Floor(fenceInfo.Standing ?? 0); - } - - public static Skills GetSkillsOrDefault(this PmcData profile) - { - return profile?.Skills ?? GetDefaultSkills(); - } - - private static Skills GetDefaultSkills() - { - return new Skills - { - Common = [], - Mastering = [], - Points = 0, - }; - } - - /// - /// Recursively checks if the given item is - /// inside the stash, that is it has the stash as - /// ancestor with slotId=hideout - /// - /// Player profile - /// Item to look for - /// True if item exists inside stash - public static bool IsItemInStash(this PmcData pmcData, Item itemToCheck) - { - // Start recursive check - return pmcData.IsParentInStash(itemToCheck.Id); - } - - public static bool IsParentInStash(this PmcData pmcData, MongoId itemId) - { - // Item not found / has no parent - var item = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemId); - if (item?.ParentId is null) - { - return false; - } - - // Root level. Items parent is the stash with slotId "hideout" - if (item.ParentId == pmcData.Inventory.Stash && item.SlotId == "hideout") + // If we've found the equipment root ID, return true + if (currentItem.Id == rootId) { return true; } - // Recursive case: Check the items parent - return IsParentInStash(pmcData, item.ParentId); + // Otherwise get the parent item + currentItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == currentItem.ParentId); } - /// - /// Iterate over all bonuses and sum up all bonuses of desired type in provided profile - /// - /// Player profile - /// Bonus to sum up - /// Summed bonus value or 0 if no bonus found - public static double GetBonusValueFromProfile(this PmcData pmcProfile, BonusType desiredBonus) + return false; + } + + /// + /// Get status of a quest in player profile by its id + /// + /// Profile to search + /// Quest id to look up + /// QuestStatus enum + public static QuestStatusEnum GetQuestStatus(this PmcData pmcData, MongoId questId) + { + var quest = pmcData.Quests?.FirstOrDefault(q => q.QId == questId); + + return quest?.Status ?? QuestStatusEnum.Locked; + } + + /// + /// Use values from the profiles template to reset all body part max values + /// + /// Profile to update + /// Template used to create profile + public static void ResetMaxLimbHp(this PmcData profile, TemplateSide profileTemplate) + { + foreach (var (partKey, bodyPart) in profile.Health.BodyParts) { - var bonuses = pmcProfile?.Bonuses?.Where(b => b.Type == desiredBonus); - if (!bonuses.Any()) - { - return 0; - } - - // Sum all bonuses found above - return bonuses?.Sum(bonus => bonus?.Value ?? 0) ?? 0; - } - - public static bool PlayerIsFleaBanned(this PmcData pmcProfile, long currentTimestamp) - { - return pmcProfile?.Info?.Bans?.Any(b => b.BanType == BanType.RagFair && currentTimestamp < b.DateTime) ?? false; - } - - /// - /// Calculates the current level of a player based on their accumulated experience points. - /// This method iterates through an experience table to determine the highest level achieved - /// by comparing the player's experience against cumulative thresholds. - /// - /// Player profile - /// Experience table from globals.json - /// - /// The calculated level of the player as an integer, or null if the level cannot be determined. - /// This value is also assigned to within the provided profile. - /// - public static int? CalculateLevel(this PmcData pmcData, ExpTable[] expTable) - { - var accExp = 0; - for (var i = 0; i < expTable.Length; i++) - { - accExp += expTable[i].Experience; - - if (pmcData.Info.Experience < accExp) - { - break; - } - - pmcData.Info.Level = i + 1; - } - - return pmcData.Info.Level; - } - - /// - /// Does the provided item have a root item with the provided id - /// - /// Profile with items - /// Item to check - /// Root item id to check for - /// True when item has rootId, false when not - public static bool DoesItemHaveRootId(this PmcData pmcData, Item item, MongoId rootId) - { - var currentItem = item; - while (currentItem is not null) - { - // If we've found the equipment root ID, return true - if (currentItem.Id == rootId) - { - return true; - } - - // Otherwise get the parent item - currentItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == currentItem.ParentId); - } - - return false; - } - - /// - /// Get status of a quest in player profile by its id - /// - /// Profile to search - /// Quest id to look up - /// QuestStatus enum - public static QuestStatusEnum GetQuestStatus(this PmcData pmcData, MongoId questId) - { - var quest = pmcData.Quests?.FirstOrDefault(q => q.QId == questId); - - return quest?.Status ?? QuestStatusEnum.Locked; - } - - /// - /// Use values from the profiles template to reset all body part max values - /// - /// Profile to update - /// Template used to create profile - public static void ResetMaxLimbHp(this PmcData profile, TemplateSide profileTemplate) - { - foreach (var (partKey, bodyPart) in profile.Health.BodyParts) - { - bodyPart.Health.Maximum = profileTemplate.Character.Health.BodyParts[partKey].Health.Maximum; - } + bodyPart.Health.Maximum = profileTemplate.Character.Health.BodyParts[partKey].Health.Maximum; } } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/QuestConditionExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/QuestConditionExtensions.cs index 9d2a3a5e..6d053631 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/QuestConditionExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/QuestConditionExtensions.cs @@ -1,67 +1,66 @@ using SPTarkov.Server.Core.Models.Eft.Common.Tables; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class QuestConditionExtensions { - public static class QuestConditionExtensions + /// + /// Get all quest conditions from provided list + /// + /// Input conditions + /// OPTIONAL - Additional filter code to run + /// + public static List GetQuestConditions( + this IEnumerable questConditions, + Func>? furtherFilter = null + ) { - /// - /// Get all quest conditions from provided list - /// - /// Input conditions - /// OPTIONAL - Additional filter code to run - /// - public static List GetQuestConditions( - this IEnumerable questConditions, - Func>? furtherFilter = null - ) - { - return FilterConditions(questConditions, "Quest", furtherFilter); - } + return FilterConditions(questConditions, "Quest", furtherFilter); + } - public static List GetLevelConditions( - this IEnumerable questConditions, - Func>? furtherFilter = null - ) - { - return FilterConditions(questConditions, "Level", furtherFilter); - } + public static List GetLevelConditions( + this IEnumerable questConditions, + Func>? furtherFilter = null + ) + { + return FilterConditions(questConditions, "Level", furtherFilter); + } - public static List GetLoyaltyConditions( - this IEnumerable questConditions, - Func>? furtherFilter = null - ) - { - return FilterConditions(questConditions, "TraderLoyalty", furtherFilter); - } + public static List GetLoyaltyConditions( + this IEnumerable questConditions, + Func>? furtherFilter = null + ) + { + return FilterConditions(questConditions, "TraderLoyalty", furtherFilter); + } - public static List GetStandingConditions( - this IEnumerable questConditions, - Func>? furtherFilter = null - ) - { - return FilterConditions(questConditions, "TraderStanding", furtherFilter); - } + public static List GetStandingConditions( + this IEnumerable questConditions, + Func>? furtherFilter = null + ) + { + return FilterConditions(questConditions, "TraderStanding", furtherFilter); + } - private static List FilterConditions( - IEnumerable questConditions, - string questType, - Func>? furtherFilter = null - ) - { - var filteredQuests = questConditions - .Where(c => + private static List FilterConditions( + IEnumerable questConditions, + string questType, + Func>? furtherFilter = null + ) + { + var filteredQuests = questConditions + .Where(c => + { + if (c.ConditionType == questType) + // return true or run the passed in function { - if (c.ConditionType == questType) - // return true or run the passed in function - { - return furtherFilter is null || furtherFilter(c).Any(); - } + return furtherFilter is null || furtherFilter(c).Any(); + } - return false; - }) - .ToList(); + return false; + }) + .ToList(); - return filteredQuests; - } + return filteredQuests; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs index 4384d421..ec506610 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/RagfairOfferExtensions.cs @@ -1,49 +1,48 @@ using SPTarkov.Server.Core.Models.Eft.Ragfair; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class RagfairOfferExtensions { - public static class RagfairOfferExtensions + /// + /// Is the passed in offer stale - end time > passed in time + /// + /// Offer to check + /// Time to check offer against + /// True - offer is stale + public static bool IsStale(this RagfairOffer offer, long time) { - /// - /// Is the passed in offer stale - end time > passed in time - /// - /// Offer to check - /// Time to check offer against - /// True - offer is stale - public static bool IsStale(this RagfairOffer offer, long time) + return offer.EndTime < time || (offer.Quantity) < 1; + } + + /// + /// Does this offer come from a trader + /// + /// Offer to check + /// True = from trader + public static bool IsTraderOffer(this RagfairOffer offer) + { + if (offer.CreatedBy is not null) { - return offer.EndTime < time || (offer.Quantity) < 1; + return offer.CreatedBy == OfferCreator.Trader; } - /// - /// Does this offer come from a trader - /// - /// Offer to check - /// True = from trader - public static bool IsTraderOffer(this RagfairOffer offer) - { - if (offer.CreatedBy is not null) - { - return offer.CreatedBy == OfferCreator.Trader; - } + return offer.User.MemberType == MemberCategory.Trader; + } - return offer.User.MemberType == MemberCategory.Trader; + /// + /// Was this offer created by a human player + /// + /// + /// + public static bool IsPlayerOffer(this RagfairOffer offer) + { + if (offer.CreatedBy is not null) + { + return offer.CreatedBy == OfferCreator.Player; } - /// - /// Was this offer created by a human player - /// - /// - /// - public static bool IsPlayerOffer(this RagfairOffer offer) - { - if (offer.CreatedBy is not null) - { - return offer.CreatedBy == OfferCreator.Player; - } - - return false; - } + return false; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/SptModExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/SptModExtensions.cs index 2784b45e..86ebf229 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/SptModExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/SptModExtensions.cs @@ -1,14 +1,13 @@ using SPTarkov.Server.Core.Models.Spt.Mod; -namespace SPTarkov.Server.Core.Extensions -{ - public static class SptModExtensions - { - public static string GetModPath(this SptMod sptMod) - { - var relativeModPath = Path.GetRelativePath(Directory.GetCurrentDirectory(), sptMod.Directory).Replace('\\', '/'); +namespace SPTarkov.Server.Core.Extensions; - return relativeModPath; - } +public static class SptModExtensions +{ + public static string GetModPath(this SptMod sptMod) + { + var relativeModPath = Path.GetRelativePath(Directory.GetCurrentDirectory(), sptMod.Directory).Replace('\\', '/'); + + return relativeModPath; } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/StringExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/StringExtensions.cs index 13b172b0..929f20b9 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/StringExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/StringExtensions.cs @@ -1,39 +1,38 @@ using System.Text; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class StringExtensions { - public static class StringExtensions + public static string Encode(this string value, EncodeType encode) { - public static string Encode(this string value, EncodeType encode) + return encode switch { - return encode switch - { - EncodeType.BASE64 => Convert.ToBase64String(Encoding.Default.GetBytes(value)), - EncodeType.HEX => Convert.ToHexString(Encoding.Default.GetBytes(value)), - EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)), - EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)), - _ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null), - }; - } + EncodeType.BASE64 => Convert.ToBase64String(Encoding.Default.GetBytes(value)), + EncodeType.HEX => Convert.ToHexString(Encoding.Default.GetBytes(value)), + EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)), + EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)), + _ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null), + }; + } - public static string Decode(this string value, EncodeType encode) + public static string Decode(this string value, EncodeType encode) + { + return encode switch { - return encode switch - { - EncodeType.BASE64 => Encoding.UTF8.GetString(Convert.FromBase64String(value)), - EncodeType.HEX => Encoding.UTF8.GetString(Convert.FromHexString(value)), - EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)), - EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)), - _ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null), - }; - } + EncodeType.BASE64 => Encoding.UTF8.GetString(Convert.FromBase64String(value)), + EncodeType.HEX => Encoding.UTF8.GetString(Convert.FromHexString(value)), + EncodeType.ASCII => Encoding.ASCII.GetString(Encoding.Default.GetBytes(value)), + EncodeType.UTF8 => Encoding.UTF8.GetString(Encoding.Default.GetBytes(value)), + _ => throw new ArgumentOutOfRangeException(nameof(encode), encode, null), + }; + } - public enum EncodeType - { - BASE64, - HEX, - ASCII, - UTF8, - } + public enum EncodeType + { + BASE64, + HEX, + ASCII, + UTF8, } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs index 5f8ae8ba..344446d9 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/TemplateItemExtensions.cs @@ -1,80 +1,79 @@ using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class TemplateItemExtensions { - public static class TemplateItemExtensions + public static IEnumerable OfClass(this Dictionary templates, params MongoId[] baseClasses) { - public static IEnumerable OfClass(this Dictionary templates, params MongoId[] baseClasses) + return templates.Where(x => baseClasses.Contains(x.Value.Parent)).Select(x => x.Value); + } + + public static IEnumerable OfClass( + this Dictionary templates, + Func pred, + params MongoId[] baseClasses + ) + { + return templates.Where(x => baseClasses.Contains(x.Value.Parent) && pred(x.Value)).Select(x => x.Value); + } + + /// + /// Check if item is quest item + /// + /// Item to check quest status of + /// true if item is flagged as quest item + public static bool IsQuestItem(this TemplateItem templateItem) + { + if (templateItem.Properties.QuestItem.GetValueOrDefault(false)) { - return templates.Where(x => baseClasses.Contains(x.Value.Parent)).Select(x => x.Value); + return true; } - public static IEnumerable OfClass( - this Dictionary templates, - Func pred, - params MongoId[] baseClasses - ) + return false; + } + + /// + /// Get a weapons default magazine template id + /// + /// Weapon to get default magazine for + /// Tpl of magazine + public static MongoId? GetWeaponsDefaultMagazineTpl(this TemplateItem weaponTemplate) + { + return weaponTemplate.Properties.DefMagType; + } + + /// + /// Get the default plate an armor has in its db item + /// + /// Item to look up default plate + /// front/back + /// Tpl of plate + public static MongoId? GetDefaultPlateTpl(this TemplateItem armorItem, string modSlot) + { + var relatedItemDbModSlot = armorItem.Properties.Slots?.FirstOrDefault(slot => + string.Equals(slot.Name, modSlot, StringComparison.OrdinalIgnoreCase) + ); + + return relatedItemDbModSlot?.Props?.Filters?.FirstOrDefault()?.Plate; + } + + /// + /// Does the passed in lack slots, cartridges or chambers + /// + /// Item to check + /// True if it lacks cartridges/chamber slots, False if not + public static bool HasNoSlotsCartridgesOrChambers(this TemplateItem item) + { + if (item.Properties is null) { - return templates.Where(x => baseClasses.Contains(x.Value.Parent) && pred(x.Value)).Select(x => x.Value); + return true; } - /// - /// Check if item is quest item - /// - /// Item to check quest status of - /// true if item is flagged as quest item - public static bool IsQuestItem(this TemplateItem templateItem) - { - if (templateItem.Properties.QuestItem.GetValueOrDefault(false)) - { - return true; - } - - return false; - } - - /// - /// Get a weapons default magazine template id - /// - /// Weapon to get default magazine for - /// Tpl of magazine - public static MongoId? GetWeaponsDefaultMagazineTpl(this TemplateItem weaponTemplate) - { - return weaponTemplate.Properties.DefMagType; - } - - /// - /// Get the default plate an armor has in its db item - /// - /// Item to look up default plate - /// front/back - /// Tpl of plate - public static MongoId? GetDefaultPlateTpl(this TemplateItem armorItem, string modSlot) - { - var relatedItemDbModSlot = armorItem.Properties.Slots?.FirstOrDefault(slot => - string.Equals(slot.Name, modSlot, StringComparison.OrdinalIgnoreCase) - ); - - return relatedItemDbModSlot?.Props?.Filters?.FirstOrDefault()?.Plate; - } - - /// - /// Does the passed in lack slots, cartridges or chambers - /// - /// Item to check - /// True if it lacks cartridges/chamber slots, False if not - public static bool HasNoSlotsCartridgesOrChambers(this TemplateItem item) - { - if (item.Properties is null) - { - return true; - } - - return item.Properties.Slots is null - || !item.Properties.Slots.Any() - && (item.Properties.Cartridges is null || !item.Properties.Cartridges.Any()) - && (item.Properties.Chambers is null || !item.Properties.Chambers.Any()); - } + return item.Properties.Slots is null + || !item.Properties.Slots.Any() + && (item.Properties.Cartridges is null || !item.Properties.Cartridges.Any()) + && (item.Properties.Chambers is null || !item.Properties.Chambers.Any()); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/TraderAssortExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/TraderAssortExtensions.cs index 643032a8..b26e3c54 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/TraderAssortExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/TraderAssortExtensions.cs @@ -1,51 +1,50 @@ using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; -namespace SPTarkov.Server.Core.Extensions +namespace SPTarkov.Server.Core.Extensions; + +public static class TraderAssortExtensions { - public static class TraderAssortExtensions + /// + /// Remove an item from an assort + /// Must be removed from the assorts; items + barterScheme + LoyaltyLevel + /// + /// Assort to remove item from + /// Id of item to remove from assort + /// Is the assort being modified the flea market assort + /// Modified assort + public static TraderAssort RemoveItemFromAssort(this TraderAssort assort, MongoId itemId, bool isFlea = false) { - /// - /// Remove an item from an assort - /// Must be removed from the assorts; items + barterScheme + LoyaltyLevel - /// - /// Assort to remove item from - /// Id of item to remove from assort - /// Is the assort being modified the flea market assort - /// Modified assort - public static TraderAssort RemoveItemFromAssort(this TraderAssort assort, MongoId itemId, bool isFlea = false) + // Flea assort needs special handling, item must remain in assort but be flagged as locked + if (isFlea && assort.BarterScheme.TryGetValue(itemId, out var listToUse)) { - // Flea assort needs special handling, item must remain in assort but be flagged as locked - if (isFlea && assort.BarterScheme.TryGetValue(itemId, out var listToUse)) + foreach (var barterScheme in listToUse.SelectMany(barterSchemes => barterSchemes)) { - foreach (var barterScheme in listToUse.SelectMany(barterSchemes => barterSchemes)) - { - barterScheme.SptQuestLocked = true; - } - - return assort; + barterScheme.SptQuestLocked = true; } - assort.BarterScheme.Remove(itemId); - assort.LoyalLevelItems.Remove(itemId); - - // The item being removed may have children linked to it, find and remove them too - var idsToRemove = assort.Items.GetItemWithChildrenTpls(itemId).ToHashSet(); - assort.Items.RemoveAll(item => idsToRemove.Contains(item.Id)); - return assort; } - /// - /// Given the blacklist provided, remove root items from assort - /// - /// Trader assort to modify - /// Item TPLs the assort should not have - public static void RemoveItemsFromAssort(this TraderAssort assortToFilter, HashSet itemsTplsToRemove) - { - assortToFilter.Items = assortToFilter - .Items.Where(item => item.ParentId == "hideout" && itemsTplsToRemove.Contains(item.Template)) - .ToList(); - } + assort.BarterScheme.Remove(itemId); + assort.LoyalLevelItems.Remove(itemId); + + // The item being removed may have children linked to it, find and remove them too + var idsToRemove = assort.Items.GetItemWithChildrenTpls(itemId).ToHashSet(); + assort.Items.RemoveAll(item => idsToRemove.Contains(item.Id)); + + return assort; + } + + /// + /// Given the blacklist provided, remove root items from assort + /// + /// Trader assort to modify + /// Item TPLs the assort should not have + public static void RemoveItemsFromAssort(this TraderAssort assortToFilter, HashSet itemsTplsToRemove) + { + assortToFilter.Items = assortToFilter + .Items.Where(item => item.ParentId == "hideout" && itemsTplsToRemove.Contains(item.Template)) + .ToList(); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/UtilityExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/UtilityExtensions.cs index 4bb92906..76026cac 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/UtilityExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/UtilityExtensions.cs @@ -1,13 +1,12 @@ -namespace SPTarkov.Server.Core.Extensions -{ - public static class UtilityExtensions - { - public static IEnumerable IntersectWith(this IEnumerable first, IEnumerable second) - { - //a.Intersect(x => b.Contains(x)).ToList(); - // gives error Delegate type could not be inferred +namespace SPTarkov.Server.Core.Extensions; - return first.Where(second.Contains); - } +public static class UtilityExtensions +{ + public static IEnumerable IntersectWith(this IEnumerable first, IEnumerable second) + { + //a.Intersect(x => b.Contains(x)).ToList(); + // gives error Delegate type could not be inferred + + return first.Where(second.Contains); } } diff --git a/Libraries/SPTarkov.Server.Core/Extensions/WildSpawnTypeExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/WildSpawnTypeExtensions.cs index 13e6362d..d2355a3b 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/WildSpawnTypeExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/WildSpawnTypeExtensions.cs @@ -1,36 +1,35 @@ using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Models.Eft.Common; -namespace SPTarkov.Server.Core.Extensions -{ - public static class WildSpawnTypeExtensions - { - /// - /// Is the passed in bot role a PMC (USEC/Bear/PMC) - /// - /// bot role to check - /// true if is pmc - public static bool IsPmc(this WildSpawnType botRole) - { - return botRole is WildSpawnType.pmcBEAR or WildSpawnType.pmcUSEC; - } +namespace SPTarkov.Server.Core.Extensions; - /// - /// Get the corresponding side when pmcBEAR or pmcUSEC is passed in - /// - /// role to get side for - /// Usec/Bear - public static string? GetPmcSideByRole(this WildSpawnType botRole) +public static class WildSpawnTypeExtensions +{ + /// + /// Is the passed in bot role a PMC (USEC/Bear/PMC) + /// + /// bot role to check + /// true if is pmc + public static bool IsPmc(this WildSpawnType botRole) + { + return botRole is WildSpawnType.pmcBEAR or WildSpawnType.pmcUSEC; + } + + /// + /// Get the corresponding side when pmcBEAR or pmcUSEC is passed in + /// + /// role to get side for + /// Usec/Bear + public static string? GetPmcSideByRole(this WildSpawnType botRole) + { + switch (botRole) { - switch (botRole) - { - case WildSpawnType.pmcBEAR: - return Sides.Bear; - case WildSpawnType.pmcUSEC: - return Sides.Usec; - default: - return null; - } + case WildSpawnType.pmcBEAR: + return Sides.Bear; + case WildSpawnType.pmcUSEC: + return Sides.Usec; + default: + return null; } } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs b/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs index ec072ce5..9f154dce 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/AbstractProfileMigration.cs @@ -1,40 +1,39 @@ using System.Text.Json.Nodes; using SPTarkov.Server.Core.Models.Eft.Profile; -namespace SPTarkov.Server.Core.Migration +namespace SPTarkov.Server.Core.Migration; + +public abstract class AbstractProfileMigration : IProfileMigration { - public abstract class AbstractProfileMigration : IProfileMigration + public abstract string FromVersion { get; } + public abstract string ToVersion { get; } + public abstract string MigrationName { get; } + + public abstract IEnumerable PrerequisiteMigrations { get; } + + public abstract bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations); + + public virtual JsonObject? Migrate(JsonObject profile) { - public abstract string FromVersion { get; } - public abstract string ToVersion { get; } - public abstract string MigrationName { get; } + return profile; + } - public abstract IEnumerable PrerequisiteMigrations { get; } + public virtual bool PostMigrate(SptProfile profile) + { + return true; + } - public abstract bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations); + protected SemanticVersioning.Version? GetProfileVersion(JsonObject profile) + { + var versionString = profile["spt"]?["version"]?.GetValue(); - public virtual JsonObject? Migrate(JsonObject profile) + if (versionString is null) { - return profile; + return null; } - public virtual bool PostMigrate(SptProfile profile) - { - return true; - } + var versionNumber = versionString.Split(' ')[0]; - protected SemanticVersioning.Version? GetProfileVersion(JsonObject profile) - { - var versionString = profile["spt"]?["version"]?.GetValue(); - - if (versionString is null) - { - return null; - } - - var versionNumber = versionString.Split(' ')[0]; - - return SemanticVersioning.Version.TryParse(versionNumber, out var version) ? version : null; - } + return SemanticVersioning.Version.TryParse(versionNumber, out var version) ? version : null; } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs b/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs index 348045c8..899a99ca 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/IProfileMigration.cs @@ -1,30 +1,29 @@ using System.Text.Json.Nodes; using SPTarkov.Server.Core.Models.Eft.Profile; -namespace SPTarkov.Server.Core.Migration +namespace SPTarkov.Server.Core.Migration; + +public interface IProfileMigration { - public interface IProfileMigration - { - /// - /// Allows for adding checks if the profile in question can migrate - /// - /// The profile to check - /// - /// Returns true if the profile can migrate, returns false if not - public bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations); + /// + /// Allows for adding checks if the profile in question can migrate + /// + /// The profile to check + /// + /// Returns true if the profile can migrate, returns false if not + public bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations); - /// - /// Migrate the profile, this should be used to handle and fix old data that has been removed from the record - /// or a general incompatibility due to different typing - /// - /// The profile to migrate - /// Returns the migrated profile on success, or null if it failed - public JsonObject? Migrate(JsonObject profile); + /// + /// Migrate the profile, this should be used to handle and fix old data that has been removed from the record + /// or a general incompatibility due to different typing + /// + /// The profile to migrate + /// Returns the migrated profile on success, or null if it failed + public JsonObject? Migrate(JsonObject profile); - /// - /// Handles post migration of the profile, this can be used to fill new types with (old) data gotten from - /// - /// Should return true if successful, should return false if not - public bool PostMigrate(SptProfile profile); - } + /// + /// Handles post migration of the profile, this can be used to fill new types with (old) data gotten from + /// + /// Should return true if successful, should return false if not + public bool PostMigrate(SptProfile profile); } diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/HideoutSeed.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/HideoutSeed.cs index 90a46d5f..dd3d3e69 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/HideoutSeed.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/HideoutSeed.cs @@ -3,53 +3,52 @@ using System.Text.Json.Nodes; using SPTarkov.DI.Annotations; using Range = SemanticVersioning.Range; -namespace SPTarkov.Server.Core.Migration.Migrations +namespace SPTarkov.Server.Core.Migration.Migrations; + +/// +/// In 0.16.1.3.35312 BSG changed this to from an int to a hex64 encoded value. +/// +[Injectable] +public class HideoutSeed : AbstractProfileMigration { - /// - /// In 0.16.1.3.35312 BSG changed this to from an int to a hex64 encoded value. - /// - [Injectable] - public class HideoutSeed : AbstractProfileMigration + public override string FromVersion { - public override string FromVersion - { - get { return "~3.10"; } - } + get { return "~3.10"; } + } - public override string ToVersion - { - get { return "3.11"; } - } + public override string ToVersion + { + get { return "3.11"; } + } - public override string MigrationName - { - get { return "HideoutSeed311-SPTSharp"; } - } + public override string MigrationName + { + get { return "HideoutSeed311-SPTSharp"; } + } - public override IEnumerable PrerequisiteMigrations - { - get { return []; } - } + public override IEnumerable PrerequisiteMigrations + { + get { return []; } + } - public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) - { - var profileVersion = GetProfileVersion(profile); - var fromRange = Range.Parse(FromVersion); - var profileVersionMatches = fromRange.IsSatisfied(profileVersion); + public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) + { + var profileVersion = GetProfileVersion(profile); + var fromRange = Range.Parse(FromVersion); + var profileVersionMatches = fromRange.IsSatisfied(profileVersion); - var seedNode = profile["characters"]?["pmc"]?["Hideout"]?["Seed"]; + var seedNode = profile["characters"]?["pmc"]?["Hideout"]?["Seed"]; - // Check if the seed still has it's numeric value, this is not valid anymore - var seedIsNumeric = seedNode is JsonValue seedValue && seedValue.TryGetValue(out _); + // Check if the seed still has it's numeric value, this is not valid anymore + var seedIsNumeric = seedNode is JsonValue seedValue && seedValue.TryGetValue(out _); - return profileVersionMatches && seedIsNumeric; - } + return profileVersionMatches && seedIsNumeric; + } - public override JsonObject? Migrate(JsonObject profile) - { - profile["characters"]!["pmc"]!["Hideout"]!["Seed"] = Convert.ToHexStringLower(RandomNumberGenerator.GetBytes(16)); + public override JsonObject? Migrate(JsonObject profile) + { + profile["characters"]!["pmc"]!["Hideout"]!["Seed"] = Convert.ToHexStringLower(RandomNumberGenerator.GetBytes(16)); - return base.Migrate(profile); - } + return base.Migrate(profile); } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenMinorFixes.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenMinorFixes.cs index 6a364ebb..f6d34576 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenMinorFixes.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenMinorFixes.cs @@ -2,53 +2,52 @@ using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Models.Eft.Profile; -namespace SPTarkov.Server.Core.Migration.Migrations +namespace SPTarkov.Server.Core.Migration.Migrations; + +/// +/// In the minor versions of 3.10 or somewhere in between these properties were added, it's possible that a profile has not updated +/// To these thus never having received them, re-add them here. +/// +[Injectable] +public class ThreeTenMinorFixes : AbstractProfileMigration { - /// - /// In the minor versions of 3.10 or somewhere in between these properties were added, it's possible that a profile has not updated - /// To these thus never having received them, re-add them here. - /// - [Injectable] - public class ThreeTenMinorFixes : AbstractProfileMigration + public override string FromVersion { - public override string FromVersion + get { return "~3.10"; } + } + + public override string ToVersion + { + get { return "3.11"; } + } + + public override string MigrationName + { + get { return "ThreeTenMinorFixes-SPTSharp"; } + } + + public override IEnumerable PrerequisiteMigrations + { + get { return [typeof(HideoutSeed)]; } + } + + public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) + { + var cultistRewardsMissing = profile["spt"]?["cultistRewards"] == null; + var friendProfileIdsMissing = profile["friends"] == null; + + return cultistRewardsMissing || friendProfileIdsMissing; + } + + public override bool PostMigrate(SptProfile profile) + { + if (profile.SptData!.CultistRewards == null) { - get { return "~3.10"; } + profile.SptData.CultistRewards = []; } - public override string ToVersion - { - get { return "3.11"; } - } + profile.FriendProfileIds ??= []; - public override string MigrationName - { - get { return "ThreeTenMinorFixes-SPTSharp"; } - } - - public override IEnumerable PrerequisiteMigrations - { - get { return [typeof(HideoutSeed)]; } - } - - public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) - { - var cultistRewardsMissing = profile["spt"]?["cultistRewards"] == null; - var friendProfileIdsMissing = profile["friends"] == null; - - return cultistRewardsMissing || friendProfileIdsMissing; - } - - public override bool PostMigrate(SptProfile profile) - { - if (profile.SptData!.CultistRewards == null) - { - profile.SptData.CultistRewards = []; - } - - profile.FriendProfileIds ??= []; - - return base.PostMigrate(profile); - } + return base.PostMigrate(profile); } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenToThreeEleven.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenToThreeEleven.cs index b2d6f86e..09971f0e 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenToThreeEleven.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/3.11/ThreeTenToThreeEleven.cs @@ -8,224 +8,215 @@ using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Services; using Range = SemanticVersioning.Range; -namespace SPTarkov.Server.Core.Migration.Migrations +namespace SPTarkov.Server.Core.Migration.Migrations; + +[Injectable] +public class ThreeTenToThreeEleven( + DatabaseService databaseService, + // Yes, referencing the helpers directly causes a circular dependency. Too bad! + IServiceProvider serviceProvider +) : AbstractProfileMigration { - [Injectable] - public class ThreeTenToThreeEleven( - DatabaseService databaseService, - // Yes, referencing the helpers directly causes a circular dependency. Too bad! - IServiceProvider serviceProvider - ) : AbstractProfileMigration + private List _oldSuiteData = []; + + public override string FromVersion { - private List _oldSuiteData = []; + get { return "~3.10"; } + } - public override string FromVersion + public override string ToVersion + { + get { return "3.11"; } + } + + public override string MigrationName + { + get { return "310x-SPTSharp"; } + } + + public override IEnumerable PrerequisiteMigrations + { + get { return [typeof(HideoutSeed)]; } + } + + public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) + { + var profileVersion = GetProfileVersion(profile); + + var fromRange = Range.Parse(FromVersion); + + var versionMatches = fromRange.IsSatisfied(profileVersion); + + return versionMatches; + } + + public override JsonObject? Migrate(JsonObject profile) + { + if (profile["suits"] is JsonArray suitsArray) { - get { return "~3.10"; } + _oldSuiteData = suitsArray.Select(node => node?.GetValue()).Where(suit => suit != null).ToList()!; } - public override string ToVersion + profile.Remove("suits"); + + return profile; + } + + public override bool PostMigrate(SptProfile profile) + { + if (profile.CustomisationUnlocks is null) { - get { return "3.11"; } + profile.CustomisationUnlocks = []; + profile.AddCustomisationUnlocksToProfile(); } - public override string MigrationName + profile.CharacterData.PmcData.Prestige ??= []; + + if (profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId is null) { - get { return "310x-SPTSharp"; } + profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId = new("676db384777490e23c45b657"); + + //Directly injecting CreateProfileService causes a circular dependency which I can't be bothered to fix just for this + (serviceProvider.GetService(typeof(CreateProfileService)) as CreateProfileService)!.AddMissingInternalContainersToProfile( + profile.CharacterData.PmcData + ); } - public override IEnumerable PrerequisiteMigrations + if (profile.CharacterData.PmcData.Hideout.Customization is null) { - get { return [typeof(HideoutSeed)]; } - } - - public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) - { - var profileVersion = GetProfileVersion(profile); - - var fromRange = Range.Parse(FromVersion); - - var versionMatches = fromRange.IsSatisfied(profileVersion); - - return versionMatches; - } - - public override JsonObject? Migrate(JsonObject profile) - { - if (profile["suits"] is JsonArray suitsArray) + profile.CharacterData.PmcData.Hideout.Customization = new Dictionary { - _oldSuiteData = suitsArray.Select(node => node?.GetValue()).Where(suit => suit != null).ToList()!; - } - - profile.Remove("suits"); - - return profile; + { "Wall", new("675844bdf94a97cbbe096f1a") }, + { "Floor", new("6758443ff94a97cbbe096f18") }, + { "Light", new("675fe8abbc3deae49a0b947f") }, + { "Ceiling", new("673b3f977038192ee006aa09") }, + { "ShootingRangeMark", new("67585d416c72998cf60ed85a") }, + }; } - public override bool PostMigrate(SptProfile profile) + if (profile.CharacterData.PmcData.Info.Side == "Bear") { - if (profile.CustomisationUnlocks is null) + ProcessBearProfile(profile); + } + + if (profile.CharacterData.PmcData.Info.Side == "Usec") + { + ProcessUsecProfile(profile); + } + + if (profile.CharacterData.PmcData.Achievements.Count > 0) + { + var achievementsDb = databaseService.GetTemplates().Achievements; + + foreach (var achievementId in profile.CharacterData.PmcData.Achievements.Keys) { - profile.CustomisationUnlocks = []; - profile.AddCustomisationUnlocksToProfile(); - } + var achievementDb = achievementsDb.FirstOrDefault(a => a.Id == achievementId); + var rewards = achievementDb?.Rewards; - profile.CharacterData.PmcData.Prestige ??= []; + if (rewards == null) + { + continue; + } - if (profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId is null) - { - profile.CharacterData.PmcData.Inventory.HideoutCustomizationStashId = new("676db384777490e23c45b657"); + // Only hand out the new hideout customization rewards. + var filteredRewards = rewards.Where(r => r.Type == RewardType.CustomizationDirect).ToList(); - //Directly injecting CreateProfileService causes a circular dependency which I can't be bothered to fix just for this - (serviceProvider.GetService(typeof(CreateProfileService)) as CreateProfileService)!.AddMissingInternalContainersToProfile( - profile.CharacterData.PmcData + //Directly injecting RewardHelper causes a circular dependency which I can't be bothered to fix just for this + (serviceProvider.GetService(typeof(RewardHelper)) as RewardHelper)!.ApplyRewards( + filteredRewards, + CustomisationSource.ACHIEVEMENT, + profile, + profile.CharacterData.PmcData, + achievementId ); } + } - if (profile.CharacterData.PmcData.Hideout.Customization is null) + return true; + } + + private void ProcessBearProfile(SptProfile profile) + { + // Reset clothing customization back to default as customization changed in 3.11 + profile.CharacterData.PmcData.Customization.Body = new("5cc0858d14c02e000c6bea66"); + profile.CharacterData.PmcData.Customization.Feet = new("5cc085bb14c02e000e67a5c5"); + profile.CharacterData.PmcData.Customization.Hands = new("5cc0876314c02e000c6bea6b"); + profile.CharacterData.PmcData.Customization.DogTag = new("674731c8bafff850080488bb"); + + if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness") + { + profile.CharacterData.PmcData.Customization.DogTag = new("6746fd09bafff85008048838"); + } + + if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition") + { + profile.CharacterData.PmcData.Customization.DogTag = new("67471928d17d6431550563b5"); + } + + foreach (var oldSuite in _oldSuiteData) + { + // Default Bear clothing, dont need to add this + if (oldSuite == "5cd946231388ce000d572fe3" || oldSuite == "5cd945d71388ce000a659dfb" || oldSuite == "666841a02537107dc508b704") { - profile.CharacterData.PmcData.Hideout.Customization = new Dictionary + continue; + } + + var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c"); + var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite); + + if (traderClothing != null) + { + var clothingToAdd = new CustomisationStorage { - { "Wall", new("675844bdf94a97cbbe096f1a") }, - { "Floor", new("6758443ff94a97cbbe096f18") }, - { "Light", new("675fe8abbc3deae49a0b947f") }, - { "Ceiling", new("673b3f977038192ee006aa09") }, - { "ShootingRangeMark", new("67585d416c72998cf60ed85a") }, + Id = traderClothing.SuiteId, + Source = CustomisationSource.UNLOCKED_IN_GAME, + Type = CustomisationType.SUITE, }; - } - if (profile.CharacterData.PmcData.Info.Side == "Bear") - { - ProcessBearProfile(profile); - } - - if (profile.CharacterData.PmcData.Info.Side == "Usec") - { - ProcessUsecProfile(profile); - } - - if (profile.CharacterData.PmcData.Achievements.Count > 0) - { - var achievementsDb = databaseService.GetTemplates().Achievements; - - foreach (var achievementId in profile.CharacterData.PmcData.Achievements.Keys) - { - var achievementDb = achievementsDb.FirstOrDefault(a => a.Id == achievementId); - var rewards = achievementDb?.Rewards; - - if (rewards == null) - { - continue; - } - - // Only hand out the new hideout customization rewards. - var filteredRewards = rewards.Where(r => r.Type == RewardType.CustomizationDirect).ToList(); - - //Directly injecting RewardHelper causes a circular dependency which I can't be bothered to fix just for this - (serviceProvider.GetService(typeof(RewardHelper)) as RewardHelper)!.ApplyRewards( - filteredRewards, - CustomisationSource.ACHIEVEMENT, - profile, - profile.CharacterData.PmcData, - achievementId - ); - } - } - - return true; - } - - private void ProcessBearProfile(SptProfile profile) - { - // Reset clothing customization back to default as customization changed in 3.11 - profile.CharacterData.PmcData.Customization.Body = new("5cc0858d14c02e000c6bea66"); - profile.CharacterData.PmcData.Customization.Feet = new("5cc085bb14c02e000e67a5c5"); - profile.CharacterData.PmcData.Customization.Hands = new("5cc0876314c02e000c6bea6b"); - profile.CharacterData.PmcData.Customization.DogTag = new("674731c8bafff850080488bb"); - - if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness") - { - profile.CharacterData.PmcData.Customization.DogTag = new("6746fd09bafff85008048838"); - } - - if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition") - { - profile.CharacterData.PmcData.Customization.DogTag = new("67471928d17d6431550563b5"); - } - - foreach (var oldSuite in _oldSuiteData) - { - // Default Bear clothing, dont need to add this - if ( - oldSuite == "5cd946231388ce000d572fe3" - || oldSuite == "5cd945d71388ce000a659dfb" - || oldSuite == "666841a02537107dc508b704" - ) - { - continue; - } - - var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c"); - var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite); - - if (traderClothing != null) - { - var clothingToAdd = new CustomisationStorage - { - Id = traderClothing.SuiteId, - Source = CustomisationSource.UNLOCKED_IN_GAME, - Type = CustomisationType.SUITE, - }; - - profile.CustomisationUnlocks.Add(clothingToAdd); - } + profile.CustomisationUnlocks.Add(clothingToAdd); } } + } - private void ProcessUsecProfile(SptProfile profile) + private void ProcessUsecProfile(SptProfile profile) + { + // Reset clothing customization back to default as customization changed in 3.11 + profile.CharacterData.PmcData.Customization.Body = new("5cde95d97d6c8b647a3769b0"); //Usec default clothing + profile.CharacterData.PmcData.Customization.Feet = new("5cde95ef7d6c8b04713c4f2d"); + profile.CharacterData.PmcData.Customization.Hands = new("5cde95fa7d6c8b04737c2d13"); + profile.CharacterData.PmcData.Customization.DogTag = new("674731d1170146228c0d222a"); //Usec base dogtag + + if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness") { - // Reset clothing customization back to default as customization changed in 3.11 - profile.CharacterData.PmcData.Customization.Body = new("5cde95d97d6c8b647a3769b0"); //Usec default clothing - profile.CharacterData.PmcData.Customization.Feet = new("5cde95ef7d6c8b04713c4f2d"); - profile.CharacterData.PmcData.Customization.Hands = new("5cde95fa7d6c8b04737c2d13"); - profile.CharacterData.PmcData.Customization.DogTag = new("674731d1170146228c0d222a"); //Usec base dogtag + profile.CharacterData.PmcData.Customization.DogTag = new("67471938bafff850080488b7"); + } - if (profile.CharacterData.PmcData.Info.GameVersion == "edge_of_darkness") + if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition") + { + profile.CharacterData.PmcData.Customization.DogTag = new("6747193f170146228c0d2226"); + } + + foreach (var oldSuite in _oldSuiteData) + { + // Default Usec clothing, dont need to add this + if (oldSuite == "5cde9ec17d6c8b04723cf479" || oldSuite == "5cde9e957d6c8b0474535da7" || oldSuite == "666841a02537107dc508b704") { - profile.CharacterData.PmcData.Customization.DogTag = new("67471938bafff850080488b7"); + continue; } - if (profile.CharacterData.PmcData.Info.GameVersion == "unheard_edition") + var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c"); + var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite); + + if (traderClothing != null) { - profile.CharacterData.PmcData.Customization.DogTag = new("6747193f170146228c0d2226"); - } - - foreach (var oldSuite in _oldSuiteData) - { - // Default Usec clothing, dont need to add this - if ( - oldSuite == "5cde9ec17d6c8b04723cf479" - || oldSuite == "5cde9e957d6c8b0474535da7" - || oldSuite == "666841a02537107dc508b704" - ) + var clothingToAdd = new CustomisationStorage { - continue; - } + Id = traderClothing.SuiteId, + Source = CustomisationSource.UNLOCKED_IN_GAME, + Type = CustomisationType.SUITE, + }; - var trader = databaseService.GetTrader("5ac3b934156ae10c4430e83c"); - var traderClothing = trader?.Suits?.FirstOrDefault(s => s.SuiteId == oldSuite); - - if (traderClothing != null) - { - var clothingToAdd = new CustomisationStorage - { - Id = traderClothing.SuiteId, - Source = CustomisationSource.UNLOCKED_IN_GAME, - Type = CustomisationType.SUITE, - }; - - profile.CustomisationUnlocks.Add(clothingToAdd); - } + profile.CustomisationUnlocks.Add(clothingToAdd); } } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/TheVoices.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/TheVoices.cs index 588fac1a..5154e613 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/TheVoices.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/TheVoices.cs @@ -2,84 +2,83 @@ using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Services; -namespace SPTarkov.Server.Core.Migration.Migrations +namespace SPTarkov.Server.Core.Migration.Migrations; + +/// +/// In 16.8.0.37972 BSG added customization for voices, technically this only affects BE profiles, but this should fix these. +/// +[Injectable] +public class TheVoices(DatabaseService databaseService) : AbstractProfileMigration { - /// - /// In 16.8.0.37972 BSG added customization for voices, technically this only affects BE profiles, but this should fix these. - /// - [Injectable] - public class TheVoices(DatabaseService databaseService) : AbstractProfileMigration + private bool _pmcVoiceIsMissing = false; + private bool _scavVoiceIsMissing = false; + + public override string FromVersion { - private bool _pmcVoiceIsMissing = false; - private bool _scavVoiceIsMissing = false; + get { return "~4.0"; } + } - public override string FromVersion + public override string ToVersion + { + get { return "~4.0"; } + } + + public override string MigrationName + { + get { return "TheVoices400"; } + } + + public override IEnumerable PrerequisiteMigrations + { + // Requires ThreeTenToThreeEleven on legacy profiles, due to that adding the customization object for the first time + get { return [typeof(ThreeTenToThreeEleven)]; } + } + + public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) + { + _pmcVoiceIsMissing = profile["characters"]?["pmc"]?["Customization"]?["Voice"] == null; + + _scavVoiceIsMissing = profile["characters"]?["scav"]?["Customization"]?["Voice"] == null; + + return _pmcVoiceIsMissing || _scavVoiceIsMissing; + } + + public override JsonObject? Migrate(JsonObject profile) + { + if (_pmcVoiceIsMissing) { - get { return "~4.0"; } + HandlePmcVoice(profile); } - public override string ToVersion + if (_scavVoiceIsMissing) { - get { return "~4.0"; } + HandleScavVoice(profile); } - public override string MigrationName - { - get { return "TheVoices400"; } - } + return base.Migrate(profile); + } - public override IEnumerable PrerequisiteMigrations - { - // Requires ThreeTenToThreeEleven on legacy profiles, due to that adding the customization object for the first time - get { return [typeof(ThreeTenToThreeEleven)]; } - } + private void HandlePmcVoice(JsonObject profileObject) + { + var pmcInfo = profileObject["characters"]!["pmc"]!["Info"] as JsonObject; - public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) - { - _pmcVoiceIsMissing = profile["characters"]?["pmc"]?["Customization"]?["Voice"] == null; + var oldVoice = pmcInfo["Voice"]?.ToString() ?? ""; + pmcInfo.Remove("Voice"); - _scavVoiceIsMissing = profile["characters"]?["scav"]?["Customization"]?["Voice"] == null; + var voiceMongoId = databaseService.GetCustomization().FirstOrDefault(x => x.Value.Properties.Name == oldVoice).Key; - return _pmcVoiceIsMissing || _scavVoiceIsMissing; - } + profileObject["characters"]!["pmc"]!["Customization"]!["Voice"] = voiceMongoId.ToString(); + } - public override JsonObject? Migrate(JsonObject profile) - { - if (_pmcVoiceIsMissing) - { - HandlePmcVoice(profile); - } + private void HandleScavVoice(JsonObject profileObject) + { + var pmcInfo = profileObject["characters"]!["scav"]!["Info"] as JsonObject; - if (_scavVoiceIsMissing) - { - HandleScavVoice(profile); - } + var oldVoice = pmcInfo["Voice"]?.ToString() ?? ""; + pmcInfo.Remove("Voice"); - return base.Migrate(profile); - } + var voiceMongoId = databaseService.GetCustomization().FirstOrDefault(x => x.Value.Properties.Name == oldVoice).Key; - private void HandlePmcVoice(JsonObject profileObject) - { - var pmcInfo = profileObject["characters"]!["pmc"]!["Info"] as JsonObject; - - var oldVoice = pmcInfo["Voice"]?.ToString() ?? ""; - pmcInfo.Remove("Voice"); - - var voiceMongoId = databaseService.GetCustomization().FirstOrDefault(x => x.Value.Properties.Name == oldVoice).Key; - - profileObject["characters"]!["pmc"]!["Customization"]!["Voice"] = voiceMongoId.ToString(); - } - - private void HandleScavVoice(JsonObject profileObject) - { - var pmcInfo = profileObject["characters"]!["scav"]!["Info"] as JsonObject; - - var oldVoice = pmcInfo["Voice"]?.ToString() ?? ""; - pmcInfo.Remove("Voice"); - - var voiceMongoId = databaseService.GetCustomization().FirstOrDefault(x => x.Value.Properties.Name == oldVoice).Key; - - profileObject["characters"]!["scav"]!["Customization"]!["Voice"] = voiceMongoId.ToString(); - } + profileObject["characters"]!["scav"]!["Customization"]!["Voice"] = voiceMongoId.ToString(); } } diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/ThreeElevenToFourZero.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/ThreeElevenToFourZero.cs index b5dc5e9f..c3e9e871 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/ThreeElevenToFourZero.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/4.0/ThreeElevenToFourZero.cs @@ -4,72 +4,71 @@ using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Utils; using Range = SemanticVersioning.Range; -namespace SPTarkov.Server.Core.Migration.Migrations +namespace SPTarkov.Server.Core.Migration.Migrations; + +[Injectable] +public class ThreeElevenToFourZero(Watermark watermark) : AbstractProfileMigration { - [Injectable] - public class ThreeElevenToFourZero(Watermark watermark) : AbstractProfileMigration + public override string FromVersion { - public override string FromVersion + get { return "~3.11"; } + } + + public override string ToVersion + { + get { return "4.0"; } + } + + public override string MigrationName + { + get { return "311x-SPTSharp"; } + } + + public override IEnumerable PrerequisiteMigrations + { + get { return [typeof(ThreeTenToThreeEleven)]; } + } + + public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) + { + var profileVersion = GetProfileVersion(profile); + + var fromRange = Range.Parse(FromVersion); + + var versionMatches = + fromRange.IsSatisfied(profileVersion) + || PrerequisiteMigrations.All(prereq => previouslyRanMigrations.Any(r => r.GetType() == prereq)); + + return versionMatches; + } + + public override JsonObject? Migrate(JsonObject profile) + { + if (profile["characters"]!["pmc"]!["Hideout"]!["Production"] is JsonObject production) { - get { return "~3.11"; } - } - - public override string ToVersion - { - get { return "4.0"; } - } - - public override string MigrationName - { - get { return "311x-SPTSharp"; } - } - - public override IEnumerable PrerequisiteMigrations - { - get { return [typeof(ThreeTenToThreeEleven)]; } - } - - public override bool CanMigrate(JsonObject profile, IEnumerable previouslyRanMigrations) - { - var profileVersion = GetProfileVersion(profile); - - var fromRange = Range.Parse(FromVersion); - - var versionMatches = - fromRange.IsSatisfied(profileVersion) - || PrerequisiteMigrations.All(prereq => previouslyRanMigrations.Any(r => r.GetType() == prereq)); - - return versionMatches; - } - - public override JsonObject? Migrate(JsonObject profile) - { - if (profile["characters"]!["pmc"]!["Hideout"]!["Production"] is JsonObject production) + foreach (var entry in production) { - foreach (var entry in production) + if ( + entry.Value is JsonObject productionEntry + && productionEntry["StartTimestamp"] is JsonValue startTimestampValue + && startTimestampValue.TryGetValue(out var startTimestampStr) + && long.TryParse(startTimestampStr, out var startTimestampInt) + ) { - if ( - entry.Value is JsonObject productionEntry - && productionEntry["StartTimestamp"] is JsonValue startTimestampValue - && startTimestampValue.TryGetValue(out var startTimestampStr) - && long.TryParse(startTimestampStr, out var startTimestampInt) - ) - { - productionEntry["StartTimestamp"] = startTimestampInt; - } + productionEntry["StartTimestamp"] = startTimestampInt; } } - - return base.Migrate(profile); } - public override bool PostMigrate(SptProfile profile) - { - profile.SptData.ExtraRepeatableQuests = []; + return base.Migrate(profile); + } - profile.SptData.Version = $"{watermark.GetVersionTag()} (Migrated from 3.11)"; + public override bool PostMigrate(SptProfile profile) + { + profile.SptData.ExtraRepeatableQuests = []; - return base.PostMigrate(profile); - } + profile.SptData.Version = $"{watermark.GetVersionTag()} (Migrated from 3.11)"; + + return base.PostMigrate(profile); } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Ws/WsRagfairNewRating.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Ws/WsRagfairNewRating.cs index b5fc4b0c..3fd2bd7e 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Ws/WsRagfairNewRating.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Ws/WsRagfairNewRating.cs @@ -1,13 +1,12 @@ using System.Text.Json.Serialization; -namespace SPTarkov.Server.Core.Models.Eft.Ws -{ - public record WsRagfairNewRating : WsNotificationEvent - { - [JsonPropertyName("rating")] - public double? Rating { get; set; } +namespace SPTarkov.Server.Core.Models.Eft.Ws; - [JsonPropertyName("isRatingGrowing")] - public bool? IsRatingGrowing { get; set; } - } +public record WsRagfairNewRating : WsNotificationEvent +{ + [JsonPropertyName("rating")] + public double? Rating { get; set; } + + [JsonPropertyName("isRatingGrowing")] + public bool? IsRatingGrowing { get; set; } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Enums/Hideout/HideoutNotificationType.cs b/Libraries/SPTarkov.Server.Core/Models/Enums/Hideout/HideoutNotificationType.cs index 15665653..fc9d9906 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Enums/Hideout/HideoutNotificationType.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Enums/Hideout/HideoutNotificationType.cs @@ -1,17 +1,16 @@ -namespace SPTarkov.Server.Core.Models.Enums.Hideout +namespace SPTarkov.Server.Core.Models.Enums.Hideout; + +public enum HideoutNotificationType { - public enum HideoutNotificationType - { - None, - FuelIsLow, - NoFuel, - ReadyToConstruct, - ReadyToUpgrade, - ReadyToInstall, - ItemReady, - ItemCollected, - RepairComplete, - ScavCaseReady, - DecryptionComplete, - } + None, + FuelIsLow, + NoFuel, + ReadyToConstruct, + ReadyToUpgrade, + ReadyToInstall, + ItemReady, + ItemCollected, + RepairComplete, + ScavCaseReady, + DecryptionComplete, } diff --git a/Libraries/SPTarkov.Server.Core/Models/Enums/SkillClass.cs b/Libraries/SPTarkov.Server.Core/Models/Enums/SkillClass.cs index db68a144..38e72094 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Enums/SkillClass.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Enums/SkillClass.cs @@ -1,14 +1,13 @@ using System.Text.Json.Serialization; -namespace SPTarkov.Server.Core.Models.Enums +namespace SPTarkov.Server.Core.Models.Enums; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SkillClass { - [JsonConverter(typeof(JsonStringEnumConverter))] - public enum SkillClass - { - Physical, - Combat, - Special, - Practical, - Mental, - } + Physical, + Combat, + Special, + Practical, + Mental, } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Launcher/NicknameValidationResult.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Launcher/NicknameValidationResult.cs index b9157ec7..750c92d3 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Launcher/NicknameValidationResult.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Launcher/NicknameValidationResult.cs @@ -1,9 +1,8 @@ -namespace SPTarkov.Server.Core.Models.Spt.Launcher +namespace SPTarkov.Server.Core.Models.Spt.Launcher; + +public enum NicknameValidationResult { - public enum NicknameValidationResult - { - Taken, - Short, - Valid, - } + Taken, + Short, + Valid, } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Ragfair/CreateFleaOfferDetails.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Ragfair/CreateFleaOfferDetails.cs index 7e9e9aca..bb56eac3 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Ragfair/CreateFleaOfferDetails.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Ragfair/CreateFleaOfferDetails.cs @@ -2,48 +2,47 @@ using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; -namespace SPTarkov.Server.Core.Models.Spt.Ragfair +namespace SPTarkov.Server.Core.Models.Spt.Ragfair; + +public record CreateFleaOfferDetails { - public record CreateFleaOfferDetails - { - /// - /// Owner of the offer - /// - public MongoId UserId { get; set; } + /// + /// Owner of the offer + /// + public MongoId UserId { get; set; } - /// - /// Time offer is listed at - /// - public long Time { get; set; } + /// + /// Time offer is listed at + /// + public long Time { get; set; } - /// - /// Items in the offer - /// - public List Items { get; set; } + /// + /// Items in the offer + /// + public List Items { get; set; } - /// - /// Cost of item (currency or barter) - /// - public List BarterScheme { get; set; } + /// + /// Cost of item (currency or barter) + /// + public List BarterScheme { get; set; } - /// - /// Loyalty level needed to buy item - /// - public int LoyalLevel { get; set; } + /// + /// Loyalty level needed to buy item + /// + public int LoyalLevel { get; set; } - /// - /// Amount of item being listed - /// - public int Quantity { get; set; } + /// + /// Amount of item being listed + /// + public int Quantity { get; set; } - /// - /// Who created the offer - /// - public OfferCreator Creator { get; set; } + /// + /// Who created the offer + /// + public OfferCreator Creator { get; set; } - /// - /// Offer should be sold all in one offer - /// - public bool SellInOnePiece { get; set; } - } + /// + /// Offer should be sold all in one offer + /// + public bool SellInOnePiece { get; set; } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Services/ProfileActivityData.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Services/ProfileActivityData.cs index 99dc2834..ddb34a5d 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Services/ProfileActivityData.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Services/ProfileActivityData.cs @@ -2,32 +2,31 @@ using System.Text.Json.Serialization; using SPTarkov.Server.Core.Models.Eft.Match; using SPTarkov.Server.Core.Models.Spt.Location; -namespace SPTarkov.Server.Core.Models.Spt.Services +namespace SPTarkov.Server.Core.Models.Spt.Services; + +public class ProfileActivityData { - public class ProfileActivityData - { - public long ClientStartedTimestamp { get; set; } - public long LastActive { get; set; } - public ProfileActivityRaidData? RaidData { get; set; } = null; - public IReadOnlyList ActiveClientMods { get; set; } = []; - } - - public class ProfileActivityRaidData - { - public GetRaidConfigurationRequestData? RaidConfiguration { get; set; } = null; - public RaidChanges? RaidAdjustments { get; set; } = null; - public LocationTransit? LocationTransit { get; set; } = null; - } - - public record ProfileActiveClientMods - { - [JsonPropertyName("modName")] - public required string Name { get; init; } - - [JsonPropertyName("modGUID")] - public required string GUID { get; init; } - - [JsonPropertyName("modVersion")] - public required Version Version { get; init; } - } + public long ClientStartedTimestamp { get; set; } + public long LastActive { get; set; } + public ProfileActivityRaidData? RaidData { get; set; } = null; + public IReadOnlyList ActiveClientMods { get; set; } = []; +} + +public class ProfileActivityRaidData +{ + public GetRaidConfigurationRequestData? RaidConfiguration { get; set; } = null; + public RaidChanges? RaidAdjustments { get; set; } = null; + public LocationTransit? LocationTransit { get; set; } = null; +} + +public record ProfileActiveClientMods +{ + [JsonPropertyName("modName")] + public required string Name { get; init; } + + [JsonPropertyName("modGUID")] + public required string GUID { get; init; } + + [JsonPropertyName("modVersion")] + public required Version Version { get; init; } } diff --git a/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs b/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs index 02d381ee..9954a34b 100644 --- a/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/ProfileMigratorService.cs @@ -6,132 +6,131 @@ using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; -namespace SPTarkov.Server.Core.Services +namespace SPTarkov.Server.Core.Services; + +[Injectable(InjectionType.Singleton)] +public class ProfileMigratorService( + IEnumerable profileMigrations, + TimeUtil timeUtil, + ISptLogger logger +) { - [Injectable(InjectionType.Singleton)] - public class ProfileMigratorService( - IEnumerable profileMigrations, - TimeUtil timeUtil, - ISptLogger logger - ) + private IEnumerable _sortedMigrations = []; + + public SptProfile HandlePendingMigrations(JsonObject profile) { - private IEnumerable _sortedMigrations = []; - - public SptProfile HandlePendingMigrations(JsonObject profile) + // On the initial run, begin sorting our migrations + // This will sort it so that any non-prerequisite migrations go first + // And then all the prerequisite ones. + if (!_sortedMigrations.Any()) { - // On the initial run, begin sorting our migrations - // This will sort it so that any non-prerequisite migrations go first - // And then all the prerequisite ones. - if (!_sortedMigrations.Any()) - { - _sortedMigrations = SortMigrations(); - } - - var profileId = profile["info"]?["id"]?.GetValue(); - - // Profile is due for a wipe or a reset, do not continue here. - if ( - profile["characters"]?["pmc"]?["Info"] == null - || profile["characters"]?["scav"]?["Info"] == null - || (profile["info"]?["wipe"]?.GetValue() == true) - ) - { - return profile.Deserialize(JsonUtil.JsonSerializerOptionsNoIndent) - ?? throw new InvalidOperationException($"Could not deserialize the profile {profileId}"); - } - - var ranMigrations = new List(); - - foreach (var profileMigration in _sortedMigrations) - { - if (profileMigration.CanMigrate(profile, ranMigrations)) - { - logger.Warning($"{profileId} has a pending profile migration: {profileMigration.MigrationName}"); - - var migratedProfile = profileMigration.Migrate(profile); - - if (migratedProfile is not null) - { - profile = migratedProfile; - - ranMigrations.Add(profileMigration); - } - } - } - - var sptReadyProfile = - profile.Deserialize(JsonUtil.JsonSerializerOptionsNoIndent) - ?? throw new InvalidOperationException($"Could not deserialize the profile {profileId}"); - - foreach (var ranMigration in ranMigrations) - { - if (ranMigration.PostMigrate(sptReadyProfile)) - { - logger.Success($"{profileId} successfully ran profile migration: {ranMigration.MigrationName}"); - - if (sptReadyProfile.SptData!.Migrations is null) - { - sptReadyProfile.SptData.Migrations = []; - } - - sptReadyProfile.SptData.Migrations.Add(ranMigration.MigrationName, timeUtil.GetTimeStamp()); - } - } - - return sptReadyProfile; + _sortedMigrations = SortMigrations(); } - protected IEnumerable SortMigrations() - { - var sortedMigrations = new List(); - var visitedMigrations = new Dictionary(); - var migrationDict = profileMigrations.Cast().ToDictionary(m => m.GetType()); + var profileId = profile["info"]?["id"]?.GetValue(); - foreach (var migration in profileMigrations.Cast()) - { - VisitMigrationForSort(migration, migrationDict, visitedMigrations, sortedMigrations); - } - - return sortedMigrations; - } - - protected void VisitMigrationForSort( - AbstractProfileMigration migration, - Dictionary migrationTypeDictionary, - Dictionary visitedTypeDictionary, - List sortedMigrations + // Profile is due for a wipe or a reset, do not continue here. + if ( + profile["characters"]?["pmc"]?["Info"] == null + || profile["characters"]?["scav"]?["Info"] == null + || (profile["info"]?["wipe"]?.GetValue() == true) ) { - var migrationType = migration.GetType(); - - if (visitedTypeDictionary.TryGetValue(migrationType, out var isVisited)) - { - if (isVisited) - { - return; - } - - // Big error, two migrations should never depend on one another - throw new InvalidOperationException($"Cycle detected in migration prerequisites involving: {migrationType.Name}"); - } - - // Mark the current migration type for visiting - visitedTypeDictionary[migrationType] = false; - - foreach (var prerequisiteType in migration.PrerequisiteMigrations) - { - if (!migrationTypeDictionary.TryGetValue(prerequisiteType, out var prereqMigration)) - { - continue; - } - - // Visit the next prerequisite - VisitMigrationForSort(prereqMigration, migrationTypeDictionary, visitedTypeDictionary, sortedMigrations); - } - - // Done visiting, mark it as fully visited and add it to the sorted migrations - visitedTypeDictionary[migrationType] = true; - sortedMigrations.Add(migration); + return profile.Deserialize(JsonUtil.JsonSerializerOptionsNoIndent) + ?? throw new InvalidOperationException($"Could not deserialize the profile {profileId}"); } + + var ranMigrations = new List(); + + foreach (var profileMigration in _sortedMigrations) + { + if (profileMigration.CanMigrate(profile, ranMigrations)) + { + logger.Warning($"{profileId} has a pending profile migration: {profileMigration.MigrationName}"); + + var migratedProfile = profileMigration.Migrate(profile); + + if (migratedProfile is not null) + { + profile = migratedProfile; + + ranMigrations.Add(profileMigration); + } + } + } + + var sptReadyProfile = + profile.Deserialize(JsonUtil.JsonSerializerOptionsNoIndent) + ?? throw new InvalidOperationException($"Could not deserialize the profile {profileId}"); + + foreach (var ranMigration in ranMigrations) + { + if (ranMigration.PostMigrate(sptReadyProfile)) + { + logger.Success($"{profileId} successfully ran profile migration: {ranMigration.MigrationName}"); + + if (sptReadyProfile.SptData!.Migrations is null) + { + sptReadyProfile.SptData.Migrations = []; + } + + sptReadyProfile.SptData.Migrations.Add(ranMigration.MigrationName, timeUtil.GetTimeStamp()); + } + } + + return sptReadyProfile; + } + + protected IEnumerable SortMigrations() + { + var sortedMigrations = new List(); + var visitedMigrations = new Dictionary(); + var migrationDict = profileMigrations.Cast().ToDictionary(m => m.GetType()); + + foreach (var migration in profileMigrations.Cast()) + { + VisitMigrationForSort(migration, migrationDict, visitedMigrations, sortedMigrations); + } + + return sortedMigrations; + } + + protected void VisitMigrationForSort( + AbstractProfileMigration migration, + Dictionary migrationTypeDictionary, + Dictionary visitedTypeDictionary, + List sortedMigrations + ) + { + var migrationType = migration.GetType(); + + if (visitedTypeDictionary.TryGetValue(migrationType, out var isVisited)) + { + if (isVisited) + { + return; + } + + // Big error, two migrations should never depend on one another + throw new InvalidOperationException($"Cycle detected in migration prerequisites involving: {migrationType.Name}"); + } + + // Mark the current migration type for visiting + visitedTypeDictionary[migrationType] = false; + + foreach (var prerequisiteType in migration.PrerequisiteMigrations) + { + if (!migrationTypeDictionary.TryGetValue(prerequisiteType, out var prereqMigration)) + { + continue; + } + + // Visit the next prerequisite + VisitMigrationForSort(prereqMigration, migrationTypeDictionary, visitedTypeDictionary, sortedMigrations); + } + + // Done visiting, mark it as fully visited and add it to the sorted migrations + visitedTypeDictionary[migrationType] = true; + sortedMigrations.Add(migration); } } diff --git a/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs b/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs index 34198239..36c91f96 100644 --- a/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs +++ b/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs @@ -8,47 +8,46 @@ using SPTarkov.Server.Core.Servers.Http; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; -namespace SPTarkov.Server.Core.Status +namespace SPTarkov.Server.Core.Status; + +[Injectable(TypePriority = 0)] +public class StatusPage(TimeUtil timeUtil, ProfileActivityService profileActivityService, ConfigServer configServer) : IHttpListener { - [Injectable(TypePriority = 0)] - public class StatusPage(TimeUtil timeUtil, ProfileActivityService profileActivityService, ConfigServer configServer) : IHttpListener + protected readonly CoreConfig _coreConfig = configServer.GetConfig(); + + public bool CanHandle(MongoId sessionId, HttpRequest req) { - protected readonly CoreConfig _coreConfig = configServer.GetConfig(); + return req.Method == "GET" && req.Path.Value.Contains("/status"); + } - public bool CanHandle(MongoId sessionId, HttpRequest req) - { - return req.Method == "GET" && req.Path.Value.Contains("/status"); - } + public async Task Handle(MongoId sessionId, HttpRequest req, HttpResponse resp) + { + var sptVersion = $"SPT version: {ProgramStatics.SPT_VERSION()}"; + var debugEnabled = $"Debug enabled: {ProgramStatics.DEBUG()}"; + var modsEnabled = $"Mods enabled: {ProgramStatics.MODS()}"; + var timeStarted = $"Started : {timeUtil.GetDateTimeFromTimeStamp(_coreConfig.ServerStartTime.Value)}"; + var uptime = $"Uptime: {DateTimeOffset.UtcNow.ToUnixTimeSeconds() - _coreConfig.ServerStartTime} seconds".ToArray(); + var activeProfiles = profileActivityService.GetActiveProfileIdsWithinMinutes(30); + var activePlayerCount = $"Profiles active in last 30 minutes: {activeProfiles.Count}. {string.Join(",", activeProfiles)}"; - public async Task Handle(MongoId sessionId, HttpRequest req, HttpResponse resp) - { - var sptVersion = $"SPT version: {ProgramStatics.SPT_VERSION()}"; - var debugEnabled = $"Debug enabled: {ProgramStatics.DEBUG()}"; - var modsEnabled = $"Mods enabled: {ProgramStatics.MODS()}"; - var timeStarted = $"Started : {timeUtil.GetDateTimeFromTimeStamp(_coreConfig.ServerStartTime.Value)}"; - var uptime = $"Uptime: {DateTimeOffset.UtcNow.ToUnixTimeSeconds() - _coreConfig.ServerStartTime} seconds".ToArray(); - var activeProfiles = profileActivityService.GetActiveProfileIdsWithinMinutes(30); - var activePlayerCount = $"Profiles active in last 30 minutes: {activeProfiles.Count}. {string.Join(",", activeProfiles)}"; + resp.StatusCode = 200; + resp.ContentType = "text/html"; - resp.StatusCode = 200; - resp.ContentType = "text/html"; + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(sptVersion)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(debugEnabled)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(modsEnabled)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(sptVersion)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(debugEnabled)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(modsEnabled)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(timeStarted)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(uptime)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(activePlayerCount)); + await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(timeStarted)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(uptime)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(activePlayerCount)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - - await resp.StartAsync(); - await resp.CompleteAsync(); - } + await resp.StartAsync(); + await resp.CompleteAsync(); } }