diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json index 559540700..f9636d810 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json @@ -2,107 +2,162 @@ "acceleration": 7, "weather": { "generateWeatherAmountHours": 24, - "seasonValues": { - "default": { + "presetWeights": { + "SUNNY": { "clouds": { - "values": [ - -1, - -0.8, - -0.5, - 0.1, - 0, - 0.15, - 0.4, - 1 - ], - "weights": [ - 70, - 22, - 22, - 15, - 15, - 15, - 5, - 4 - ] + "-1": 5, + "-0.8": 2 }, "windSpeed": { - "values": [ - 0, - 1, - 2, - 3, - 4 - ], - "weights": [ - 6, - 3, - 2, - 1, - 1 - ] + "0": 6, + "1": 3, + "2": 2, + "3": 1, + "4": 1 }, "windDirection": { - "values": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8 - ], - "weights": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ] + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1 }, "windGustiness": { "min": 0, "max": 1 }, "rain": { - "values": [ - 1, - 2, - 3, - 4, - 5 - ], - "weights": [ - 20, - 1, - 1, - 1, - 1 - ] + "1": 1 + }, + "rainIntensity": { + "min": 0, + "max": 0 + }, + "fog": { + "0.0013": 35, + "0.0018": 6, + "0.002": 4, + "0.004": 3, + "0.006": 1 + }, + "temp": { + "day": { + "min": 9, + "max": 32 + }, + "night": { + "min": 2, + "max": 16 + } + }, + "pressure": { + "min": 760, + "max": 780 + } + }, + "RAINY": { + "clouds": { + "0.4": 5, + "1": 4 + }, + "windSpeed": { + "0": 6, + "1": 3, + "2": 2, + "3": 1, + "4": 1 + }, + "windDirection": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1 + }, + "windGustiness": { + "min": 0, + "max": 1 + }, + "rain": { + "2": 5, + "3": 5, + "4": 5, + "5": 5 + }, + "rainIntensity": { + "min": 0.1, + "max": 1 + }, + "fog": { + "0.0013": 35, + "0.0018": 6, + "0.002": 4, + "0.004": 3, + "0.006": 1 + }, + "temp": { + "day": { + "min": 9, + "max": 32 + }, + "night": { + "min": 2, + "max": 16 + } + }, + "pressure": { + "min": 760, + "max": 780 + } + }, + "CLOUDY": { + "clouds": { + "0.1": 10, + "0": 10, + "0.15": 10, + "0.4": 10, + "1": 10 + }, + "windSpeed": { + "0": 6, + "1": 3, + "2": 2, + "3": 1, + "4": 1 + }, + "windDirection": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1 + }, + "windGustiness": { + "min": 0, + "max": 1 + }, + "rain": { + "1": 1 }, "rainIntensity": { "min": 0, "max": 1 }, "fog": { - "values": [ - 0.0013, - 0.0018, - 0.002, - 0.004, - 0.006 - ], - "weights": [ - 35, - 6, - 4, - 3, - 1 - ] + "0.0013": 35, + "0.0018": 6, + "0.002": 4, + "0.004": 3, + "0.006": 1 }, "temp": { "day": { @@ -121,94 +176,48 @@ }, "WINTER": { "clouds": { - "values": [ - -1, - 0.65, - 1 - ], - "weights": [ - 2, - 1, - 1 - ] + "-1": 2, + "0.65": 1, + "1": 1 }, "windSpeed": { - "values": [ - 0, - 1, - 2, - 3, - 4 - ], - "weights": [ - 6, - 3, - 2, - 1, - 1 - ] + "0": 6, + "1": 3, + "2": 2, + "3": 1, + "4": 1 }, "windDirection": { - "values": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8 - ], - "weights": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ] + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1 }, "windGustiness": { "min": 0, "max": 1 }, "rain": { - "values": [ - 1, - 2, - 3, - 4, - 5 - ], - "weights": [ - 0, - 1, - 1, - 1, - 1 - ] + "1": 0, + "2": 1, + "3": 1, + "4": 1, + "5": 1 }, "rainIntensity": { "min": 0, "max": 1 }, "fog": { - "values": [ - 0.0013, - 0.0018, - 0.002, - 0.004, - 0.012 - ], - "weights": [ - 5, - 4, - 1, - 3, - 2 - ] + "0.0013": 5, + "0.0018": 4, + "0.002": 1, + "0.004": 3, + "0.012": 2 }, "temp": { "day": { @@ -235,7 +244,24 @@ 1, 2 ] - } + }, + "weatherPresetWeight": { + "WINTER_START": { + "SUNNY": 3, + "RAINY": 8, + "CLOUDY": 3 + }, + "WINTER_END": { + "SUNNY": 3, + "RAINY": 8, + "CLOUDY": 3 + }, + "default": { + "SUNNY": 7, + "RAINY": 7, + "CLOUDY": 7 + } + } }, "seasonDates": [ { diff --git a/Libraries/SPTarkov.Server.Core/Controllers/WeatherController.cs b/Libraries/SPTarkov.Server.Core/Controllers/WeatherController.cs index 3ba8d0c1f..9512ba3c0 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/WeatherController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/WeatherController.cs @@ -1,37 +1,58 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Generators; +using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Weather; -using SPTarkov.Server.Core.Models.Enums; +using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Weather; +using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; +using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Core.Controllers; [Injectable] public class WeatherController( + TimeUtil timeUtil, WeatherGenerator weatherGenerator, SeasonalEventService seasonalEventService, - RaidWeatherService raidWeatherService + RaidWeatherService raidWeatherService, + WeatherHelper weatherHelper, + ConfigServer configServer, + ICloner cloner ) { + protected readonly WeatherConfig WeatherConfig = configServer.GetConfig(); + /// /// Handle client/weather /// /// WeatherData public WeatherData Generate() { + var currentSeason = seasonalEventService.GetActiveWeatherSeason(); + + // Prep object to send to client var result = new WeatherData { Acceleration = 0, Time = string.Empty, Date = string.Empty, Weather = null, - Season = Season.AUTUMN, + Season = currentSeason, }; - weatherGenerator.CalculateGameTime(result); - result.Weather = weatherGenerator.GenerateWeather(result.Season.Value); + // Assign now in a bsg-style formatted string to result object property + result.Date = timeUtil.GetDateTimeNow().FormatToBsgDate(); + + // Get server uptime seconds multiplied by a multiplier and add to current time as seconds + result.Time = weatherHelper.GetInRaidTime().GetBsgFormattedWeatherTime(); + result.Acceleration = WeatherConfig.Acceleration; + + var presetWeights = cloner.Clone(weatherGenerator.GetWeatherPresetWeightsBySeason(currentSeason)); + result.Weather = weatherGenerator.GenerateWeather(result.Season.Value, ref presetWeights); return result; } diff --git a/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/AbstractWeatherPresetGeneratorBase.cs b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/AbstractWeatherPresetGeneratorBase.cs new file mode 100644 index 000000000..88e28cfd2 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/AbstractWeatherPresetGeneratorBase.cs @@ -0,0 +1,46 @@ +using SPTarkov.Server.Core.Helpers; +using SPTarkov.Server.Core.Models.Eft.Weather; +using SPTarkov.Server.Core.Models.Enums; +using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Models.Spt.Weather; +using SPTarkov.Server.Core.Utils; + +namespace SPTarkov.Server.Core.Generators.WeatherGen; + +public abstract class AbstractWeatherPresetGeneratorBase(WeightedRandomHelper weightedRandomHelper, RandomUtil randomUtil) + : IWeatherPresetGenerator +{ + public abstract bool CanHandle(WeatherPreset preset); + + public abstract Weather Generate(PresetWeights weatherWeights); + + protected WindDirection GetWeightedWindDirection(PresetWeights weather) + { + return weightedRandomHelper.GetWeightedValue(weather.WindDirection); + } + + protected double GetWeightedClouds(PresetWeights weather) + { + return double.Parse(weightedRandomHelper.GetWeightedValue(weather.Clouds)); + } + + protected double GetWeightedWindSpeed(PresetWeights weather) + { + return double.Parse(weightedRandomHelper.GetWeightedValue(weather.WindSpeed)); + } + + protected double GetWeightedFog(PresetWeights weather) + { + return double.Parse(weightedRandomHelper.GetWeightedValue(weather.Fog)); + } + + protected double GetWeightedRain(PresetWeights weather) + { + return double.Parse(weightedRandomHelper.GetWeightedValue(weather.Rain)); + } + + protected double GetRandomDouble(double min, double max, int precision = 3) + { + return Math.Round(randomUtil.GetDouble(min, max), precision); + } +} diff --git a/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/CloudyWeatherGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/CloudyWeatherGenerator.cs new file mode 100644 index 000000000..658212b7d --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/CloudyWeatherGenerator.cs @@ -0,0 +1,41 @@ +using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Helpers; +using SPTarkov.Server.Core.Models.Eft.Weather; +using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Utils; + +namespace SPTarkov.Server.Core.Generators.WeatherGen; + +[Injectable] +public class CloudyWeatherGenerator(WeightedRandomHelper weightedRandomHelper, RandomUtil randomUtil) + : AbstractWeatherPresetGeneratorBase(weightedRandomHelper, randomUtil) +{ + public override bool CanHandle(WeatherPreset preset) + { + return preset == WeatherPreset.CLOUDY; + } + + public override Weather Generate(PresetWeights weatherWeights) + { + var clouds = GetWeightedClouds(weatherWeights); + + var result = new Weather + { + Pressure = GetRandomDouble(weatherWeights.Pressure.Min, weatherWeights.Pressure.Max), + Temperature = 0, // Handled in caller + Fog = GetWeightedFog(weatherWeights), + RainIntensity = 0, + Rain = 0, + WindGustiness = GetRandomDouble(weatherWeights.WindGustiness.Min, weatherWeights.WindGustiness.Max, 2), + WindDirection = GetWeightedWindDirection(weatherWeights), + WindSpeed = GetWeightedWindSpeed(weatherWeights), + Cloud = clouds, + Time = string.Empty, + Date = string.Empty, + Timestamp = 0, + SptInRaidTimestamp = 0, // Handled in caller + }; + + return result; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/RainyWeatherGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/RainyWeatherGenerator.cs new file mode 100644 index 000000000..f50daa4be --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/RainyWeatherGenerator.cs @@ -0,0 +1,41 @@ +using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Helpers; +using SPTarkov.Server.Core.Models.Eft.Weather; +using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Utils; + +namespace SPTarkov.Server.Core.Generators.WeatherGen; + +[Injectable] +public class RainyWeatherGenerator(WeightedRandomHelper weightedRandomHelper, RandomUtil randomUtil) + : AbstractWeatherPresetGeneratorBase(weightedRandomHelper, randomUtil) +{ + public override bool CanHandle(WeatherPreset preset) + { + return preset == WeatherPreset.RAINY; + } + + public override Weather Generate(PresetWeights weatherWeights) + { + var clouds = GetWeightedClouds(weatherWeights); + + var result = new Weather + { + Pressure = GetRandomDouble(weatherWeights.Pressure.Min, weatherWeights.Pressure.Max), + Temperature = 0, // // Handled in caller + Fog = GetWeightedFog(weatherWeights), + RainIntensity = GetRandomDouble(weatherWeights.RainIntensity.Min, weatherWeights.RainIntensity.Max), + Rain = GetWeightedRain(weatherWeights), + WindGustiness = GetRandomDouble(weatherWeights.WindGustiness.Min, weatherWeights.WindGustiness.Max, 2), + WindDirection = GetWeightedWindDirection(weatherWeights), + WindSpeed = GetWeightedWindSpeed(weatherWeights), + Cloud = clouds, + Time = string.Empty, + Date = string.Empty, + Timestamp = 0, + SptInRaidTimestamp = 0, // Handled in caller + }; + + return result; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/SunnyWeatherGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/SunnyWeatherGenerator.cs new file mode 100644 index 000000000..048bcfcaf --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Generators/WeatherGen/SunnyWeatherGenerator.cs @@ -0,0 +1,39 @@ +using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Helpers; +using SPTarkov.Server.Core.Models.Eft.Weather; +using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Utils; + +namespace SPTarkov.Server.Core.Generators.WeatherGen; + +[Injectable] +public class SunnyWeatherGenerator(WeightedRandomHelper weightedRandomHelper, RandomUtil randomUtil) + : AbstractWeatherPresetGeneratorBase(weightedRandomHelper, randomUtil) +{ + public override bool CanHandle(WeatherPreset preset) + { + return preset == WeatherPreset.SUNNY; + } + + public override Weather Generate(PresetWeights weatherWeights) + { + var result = new Weather + { + Pressure = GetRandomDouble(weatherWeights.Pressure.Min, weatherWeights.Pressure.Max), + Temperature = 0, // Handled in caller + Fog = GetWeightedFog(weatherWeights), + RainIntensity = 0, + Rain = 0, + WindGustiness = GetRandomDouble(weatherWeights.WindGustiness.Min, weatherWeights.WindGustiness.Max, 2), + WindDirection = GetWeightedWindDirection(weatherWeights), + WindSpeed = GetWeightedWindSpeed(weatherWeights), + Cloud = GetWeightedClouds(weatherWeights), + Time = string.Empty, + Date = string.Empty, + Timestamp = 0, + SptInRaidTimestamp = 0, // Handled in caller + }; + + return result; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Generators/WeatherGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/WeatherGenerator.cs index 3f7c033f8..8120f07f3 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/WeatherGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/WeatherGenerator.cs @@ -4,98 +4,124 @@ using SPTarkov.Server.Core.Models.Eft.Weather; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Config; +using SPTarkov.Server.Core.Models.Spt.Weather; +using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; -using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Core.Generators; [Injectable] public class WeatherGenerator( + ISptLogger logger, TimeUtil timeUtil, - SeasonalEventService seasonalEventService, WeatherHelper weatherHelper, ConfigServer configServer, WeightedRandomHelper weightedRandomHelper, - RandomUtil randomUtil + RandomUtil randomUtil, + IEnumerable weatherGenerators, + ICloner cloner ) { protected readonly WeatherConfig WeatherConfig = configServer.GetConfig(); /// - /// Get current + raid datetime and format into correct BSG format. + /// Generate a weather object to send to client /// - /// Weather data - /// WeatherData - public void CalculateGameTime(WeatherData data) + /// What season is weather being generated for + /// Weather preset weights to pick from (values will be altered when generating more than 1) + /// Optional - Current time in millisecond ticks + /// Optional -What weather preset was last generated + /// A generated object + public Weather GenerateWeather( + Season currentSeason, + ref Dictionary presetWeights, + long? timestamp = null, + WeatherPreset? previousPreset = null + ) { - var computedDate = timeUtil.GetDateTimeNow(); - var formattedDate = computedDate.FormatToBsgDate(); + if (presetWeights.Count == 0) + { + // No presets, get fresh cloned weights from config + presetWeights = cloner.Clone(GetWeatherPresetWeightsBySeason(currentSeason)); + } + + // Only process when we have weights + there was previous preset chosen + if (previousPreset.HasValue && presetWeights.ContainsKey(previousPreset.Value)) + { + // We know last picked preset, Adjust weights + // Make it less likely to be picked now + // Clamp to 0 + presetWeights[previousPreset.Value] = Math.Max(0, presetWeights[previousPreset.Value] - 1); + } - data.Date = formattedDate; - data.Time = GetBsgFormattedInRaidTime(); - data.Acceleration = WeatherConfig.Acceleration; + // Assign value to previousPreset to be picked up next loop + previousPreset = weightedRandomHelper.GetWeightedValue(presetWeights); - data.Season = seasonalEventService.GetActiveWeatherSeason(); + // Check if chosen preset has been exhausted and reset if necessary + if (presetWeights[previousPreset.Value] == 0) + { + // Flag for fresh presets + presetWeights.Clear(); + } + + return GenerateWeatherByPreset(previousPreset.Value, timestamp); } /// - /// Get server uptime seconds multiplied by a multiplier and add to current time as seconds. - /// Formatted to BSGs requirements + /// Gets weather property weights for the provided season /// - /// Formatted time as String - protected string GetBsgFormattedInRaidTime() + /// Desired season to get weights for + /// A dictionary of weather preset weights + public Dictionary GetWeatherPresetWeightsBySeason(Season currentSeason) { - return weatherHelper.GetInRaidTime().GetBsgFormattedWeatherTime(); + return WeatherConfig.Weather.WeatherPresetWeight.TryGetValue(currentSeason.ToString(), out var weights) + ? weights + : WeatherConfig.Weather.WeatherPresetWeight.GetValueOrDefault("default")!; } /// - /// Return randomised Weather data with help of config/weather.json + /// Creates a object that adheres to the chosen preset /// - /// The currently active season - /// Optional, what timestamp to generate the weather data at, defaults to now when not supplied - /// Randomised weather data - public Weather GenerateWeather(Season currentSeason, long? timestamp = null) + /// The weather preset chosen to generate + /// OPTIONAL - generate the weather object with a specific time instead of now + /// A generated object + protected Weather GenerateWeatherByPreset(WeatherPreset chosenPreset, long? timestamp) { - var weatherValues = GetWeatherValuesBySeason(currentSeason); - var clouds = GetWeightedClouds(weatherValues); + var generator = weatherGenerators.FirstOrDefault(gen => gen.CanHandle(chosenPreset)); + if (generator is null) + { + logger.Warning($"Unable to find weather generator for: {chosenPreset}, falling back to sunny"); - // Force rain to off if no clouds - var rain = clouds <= 0.6 ? 0 : GetWeightedRain(weatherValues); + generator = weatherGenerators.FirstOrDefault(gen => gen.CanHandle(WeatherPreset.SUNNY)); + } - // TODO: Ensure Weather settings match Ts Server GetRandomDouble produces a decimal value way higher than ts server - var result = new Weather - { - Pressure = GetRandomDouble(weatherValues.Pressure.Min, weatherValues.Pressure.Max), - Temperature = 0, - Fog = GetWeightedFog(weatherValues), - RainIntensity = rain > 1 ? GetRandomDouble(weatherValues.RainIntensity.Min, weatherValues.RainIntensity.Max) : 0, - Rain = rain, - WindGustiness = GetRandomDouble(weatherValues.WindGustiness.Min, weatherValues.WindGustiness.Max, 2), - WindDirection = GetWeightedWindDirection(weatherValues), - WindSpeed = GetWeightedWindSpeed(weatherValues), - Cloud = clouds, - Time = string.Empty, - Date = string.Empty, - Timestamp = 0, - SptInRaidTimestamp = 0, - }; + var presetWeights = GetWeatherWeightsByPreset(chosenPreset); + var result = generator.Generate(presetWeights); + // Set time values in result using now or passed in timestamp SetCurrentDateTime(result, timestamp); - result.Temperature = GetRaidTemperature(weatherValues, result.SptInRaidTimestamp ?? 0); + // Must occur after SetCurrentDateTime(), temp depends on timestamp + result.Temperature = GetRaidTemperature(presetWeights, result.SptInRaidTimestamp ?? 0); + + // Needed by RaidWeatherService + result.SptChosenPreset = chosenPreset; return result; } - protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason) + /// + /// Get the weather preset weights based on passed in preset, get defaults if preset not found in config + /// + /// Desired preset + /// PresetWeights + protected PresetWeights GetWeatherWeightsByPreset(WeatherPreset weatherPreset) { - if (!WeatherConfig.Weather.SeasonValues.TryGetValue(currentSeason.ToString(), out var value)) - { - return WeatherConfig.Weather.SeasonValues["default"]; - } - - return value!; + return WeatherConfig.Weather.PresetWeights.TryGetValue(weatherPreset.ToString(), out var value) + ? value + : WeatherConfig.Weather.PresetWeights["default"]; } /// @@ -104,7 +130,7 @@ protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason) /// What season Tarkov is currently in /// What time is the raid running at /// Timestamp - protected double GetRaidTemperature(SeasonalValues weather, long inRaidTimestamp) + protected double GetRaidTemperature(PresetWeights weather, long inRaidTimestamp) { // Convert timestamp to date so we can get current hour and check if its day or night var currentRaidTime = new DateTime(inRaidTimestamp); @@ -130,34 +156,4 @@ protected void SetCurrentDateTime(Weather weather, long? timestamp = null) weather.Time = datetimeBsgFormat; // matches weather.timestamp weather.SptInRaidTimestamp = weather.Timestamp; } - - protected WindDirection GetWeightedWindDirection(SeasonalValues weather) - { - return weightedRandomHelper.WeightedRandom(weather.WindDirection.Values, weather.WindDirection.Weights).Item; - } - - protected double GetWeightedClouds(SeasonalValues weather) - { - return weightedRandomHelper.WeightedRandom(weather.Clouds.Values, weather.Clouds.Weights).Item; - } - - protected double GetWeightedWindSpeed(SeasonalValues weather) - { - return weightedRandomHelper.WeightedRandom(weather.WindSpeed.Values, weather.WindSpeed.Weights).Item; - } - - protected double GetWeightedFog(SeasonalValues weather) - { - return weightedRandomHelper.WeightedRandom(weather.Fog.Values, weather.Fog.Weights).Item; - } - - protected double GetWeightedRain(SeasonalValues weather) - { - return weightedRandomHelper.WeightedRandom(weather.Rain.Values, weather.Rain.Weights).Item; - } - - protected double GetRandomDouble(double min, double max, int precision = 3) - { - return Math.Round(randomUtil.GetDouble(min, max), precision); - } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Weather/WeatherData.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Weather/WeatherData.cs index 83d4addb9..398d57fc1 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Weather/WeatherData.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Weather/WeatherData.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using SPTarkov.Server.Core.Models.Enums; +using SPTarkov.Server.Core.Models.Spt.Config; namespace SPTarkov.Server.Core.Models.Eft.Weather; @@ -67,4 +68,7 @@ public record Weather [JsonPropertyName("sptInRaidTimestamp")] public long? SptInRaidTimestamp { get; set; } + + [JsonPropertyName("sptChosenPreset")] + public WeatherPreset? SptChosenPreset { get; set; } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/WeatherConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/WeatherConfig.cs index 456702c3b..020a272f4 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/WeatherConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/WeatherConfig.cs @@ -50,8 +50,8 @@ public record SeasonDateTimes public record WeatherValues { - [JsonPropertyName("seasonValues")] - public Dictionary? SeasonValues { get; set; } + [JsonPropertyName("presetWeights")] + public Dictionary? PresetWeights { get; set; } /// /// How many hours to generate weather data into the future @@ -64,30 +64,40 @@ public record WeatherValues /// [JsonPropertyName("timePeriod")] public WeatherSettings? TimePeriod { get; set; } + + [JsonPropertyName("weatherPresetWeight")] + public Dictionary> WeatherPresetWeight { get; set; } +} + +public enum WeatherPreset +{ + SUNNY = 1, + RAINY = 2, + CLOUDY = 3, } -public record SeasonalValues +public record PresetWeights { [JsonPropertyName("clouds")] - public WeatherSettings? Clouds { get; set; } + public Dictionary Clouds { get; set; } [JsonPropertyName("windSpeed")] - public WeatherSettings? WindSpeed { get; set; } + public Dictionary? WindSpeed { get; set; } [JsonPropertyName("windDirection")] - public WeatherSettings? WindDirection { get; set; } + public Dictionary? WindDirection { get; set; } [JsonPropertyName("windGustiness")] public MinMax? WindGustiness { get; set; } [JsonPropertyName("rain")] - public WeatherSettings? Rain { get; set; } + public Dictionary? Rain { get; set; } [JsonPropertyName("rainIntensity")] public MinMax? RainIntensity { get; set; } [JsonPropertyName("fog")] - public WeatherSettings? Fog { get; set; } + public Dictionary? Fog { get; set; } [JsonPropertyName("temp")] public TempDayNight? Temp { get; set; } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Weather/IWeatherPresetGenerator.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Weather/IWeatherPresetGenerator.cs new file mode 100644 index 000000000..4b8e9426e --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Weather/IWeatherPresetGenerator.cs @@ -0,0 +1,9 @@ +using SPTarkov.Server.Core.Models.Spt.Config; + +namespace SPTarkov.Server.Core.Models.Spt.Weather; + +public interface IWeatherPresetGenerator +{ + public bool CanHandle(WeatherPreset preset); + public Eft.Weather.Weather Generate(PresetWeights weatherWeights); +} diff --git a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs index 79e86c0d3..6c46cba1d 100644 --- a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs @@ -118,7 +118,7 @@ public void PerformPostDbLoadActions() AddCustomItemPresetsToGlobals(); var currentSeason = seasonalEventService.GetActiveWeatherSeason(); - raidWeatherService.GenerateWeather(currentSeason); + raidWeatherService.GenerateFutureWeatherAndCache(currentSeason); if (BotConfig.WeeklyBoss.Enabled) { diff --git a/Libraries/SPTarkov.Server.Core/Services/RaidWeatherService.cs b/Libraries/SPTarkov.Server.Core/Services/RaidWeatherService.cs index 41bb4bf67..018ee2459 100644 --- a/Libraries/SPTarkov.Server.Core/Services/RaidWeatherService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/RaidWeatherService.cs @@ -6,6 +6,7 @@ using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Cloners; namespace SPTarkov.Server.Core.Services; @@ -15,7 +16,8 @@ public class RaidWeatherService( WeatherGenerator weatherGenerator, SeasonalEventService seasonalEventService, WeightedRandomHelper weightedRandomHelper, - ConfigServer configServer + ConfigServer configServer, + ICloner cloner ) { protected readonly WeatherConfig WeatherConfig = configServer.GetConfig(); @@ -24,7 +26,7 @@ ConfigServer configServer /// /// Generate 24 hours of weather data starting from midnight today /// - public void GenerateWeather(Season currentSeason) + public void GenerateFutureWeatherAndCache(Season currentSeason) { // When to start generating weather from in milliseconds var startingTimestamp = timeUtil.GetTodayMidnightTimeStamp(); @@ -34,13 +36,21 @@ public void GenerateWeather(Season currentSeason) // Keep adding new weather until we have reached desired future date var nextTimestamp = startingTimestamp; + + // Store this so it can be passed into GenerateWeather() + WeatherPreset? previousPreset = null; + var presetWeights = cloner.Clone(weatherGenerator.GetWeatherPresetWeightsBySeason(currentSeason)); while (nextTimestamp <= futureTimestampToReach) { - var newWeatherToAddToCache = weatherGenerator.GenerateWeather(currentSeason, nextTimestamp); + // Pass by ref as method will alter weight values + var newWeatherToAddToCache = weatherGenerator.GenerateWeather(currentSeason, ref presetWeights, nextTimestamp, previousPreset); // Add generated weather for time period to cache WeatherForecast.Add(newWeatherToAddToCache); + // Store for use in next loop + previousPreset = newWeatherToAddToCache.SptChosenPreset; + // Increment timestamp so next loop can begin at correct time nextTimestamp += GetWeightedWeatherTimePeriod(); } @@ -93,7 +103,7 @@ protected void ValidateWeatherDataExists(Season currentSeason) var result = WeatherForecast.Where(weather => weather.Timestamp >= timeUtil.GetTimeStamp()); if (!result.Any()) { - GenerateWeather(currentSeason); + GenerateFutureWeatherAndCache(currentSeason); } } }