Weather refactor (#596)
* First pass at Weather generation refactor * Moved generation logic around * Added seasonal variability support * Expanded weather generation to use DI system and allow easier modding * Updated weather weight values Converted records into classes * Added fallback when generator isn't found * Fixed colliding lambda Added method comments * Cleanup of weather code * Adjusted `weatherPresetWeight` values --------- Co-authored-by: Chomp <dev@dev.sp-tarkov.com>
This commit is contained in:
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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<WeatherConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/weather
|
||||
/// </summary>
|
||||
/// <returns>WeatherData</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -4,98 +4,124 @@ 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.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<WeatherGenerator> logger,
|
||||
TimeUtil timeUtil,
|
||||
SeasonalEventService seasonalEventService,
|
||||
WeatherHelper weatherHelper,
|
||||
ConfigServer configServer,
|
||||
WeightedRandomHelper weightedRandomHelper,
|
||||
RandomUtil randomUtil
|
||||
RandomUtil randomUtil,
|
||||
IEnumerable<IWeatherPresetGenerator> weatherGenerators,
|
||||
ICloner cloner
|
||||
)
|
||||
{
|
||||
protected readonly WeatherConfig WeatherConfig = configServer.GetConfig<WeatherConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Get current + raid datetime and format into correct BSG format.
|
||||
/// Generate a weather object to send to client
|
||||
/// </summary>
|
||||
/// <param name="data"> Weather data </param>
|
||||
/// <returns> WeatherData </returns>
|
||||
public void CalculateGameTime(WeatherData data)
|
||||
/// <param name="currentSeason">What season is weather being generated for</param>
|
||||
/// <param name="presetWeights">Weather preset weights to pick from (values will be altered when generating more than 1)</param>
|
||||
/// <param name="timestamp">Optional - Current time in millisecond ticks</param>
|
||||
/// <param name="previousPreset">Optional -What weather preset was last generated</param>
|
||||
/// <returns>A generated <see cref="Weather"/> object</returns>
|
||||
public Weather GenerateWeather(
|
||||
Season currentSeason,
|
||||
ref Dictionary<WeatherPreset, double> presetWeights,
|
||||
long? timestamp = null,
|
||||
WeatherPreset? previousPreset = null
|
||||
)
|
||||
{
|
||||
var computedDate = timeUtil.GetDateTimeNow();
|
||||
var formattedDate = computedDate.FormatToBsgDate();
|
||||
|
||||
data.Date = formattedDate;
|
||||
data.Time = GetBsgFormattedInRaidTime();
|
||||
data.Acceleration = WeatherConfig.Acceleration;
|
||||
|
||||
data.Season = seasonalEventService.GetActiveWeatherSeason();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get server uptime seconds multiplied by a multiplier and add to current time as seconds.
|
||||
/// Formatted to BSGs requirements
|
||||
/// </summary>
|
||||
/// <returns>Formatted time as String </returns>
|
||||
protected string GetBsgFormattedInRaidTime()
|
||||
{
|
||||
return weatherHelper.GetInRaidTime().GetBsgFormattedWeatherTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return randomised Weather data with help of config/weather.json
|
||||
/// </summary>
|
||||
/// <param name="currentSeason"> The currently active season </param>
|
||||
/// <param name="timestamp"> Optional, what timestamp to generate the weather data at, defaults to now when not supplied </param>
|
||||
/// <returns> Randomised weather data </returns>
|
||||
public Weather GenerateWeather(Season currentSeason, long? timestamp = null)
|
||||
{
|
||||
var weatherValues = GetWeatherValuesBySeason(currentSeason);
|
||||
var clouds = GetWeightedClouds(weatherValues);
|
||||
|
||||
// Force rain to off if no clouds
|
||||
var rain = clouds <= 0.6 ? 0 : GetWeightedRain(weatherValues);
|
||||
|
||||
// TODO: Ensure Weather settings match Ts Server GetRandomDouble produces a decimal value way higher than ts server
|
||||
var result = new Weather
|
||||
if (presetWeights.Count == 0)
|
||||
{
|
||||
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,
|
||||
};
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Assign value to previousPreset to be picked up next loop
|
||||
previousPreset = weightedRandomHelper.GetWeightedValue(presetWeights);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets weather property weights for the provided season
|
||||
/// </summary>
|
||||
/// <param name="currentSeason">Desired season to get weights for</param>
|
||||
/// <returns>A dictionary of weather preset weights</returns>
|
||||
public Dictionary<WeatherPreset, double> GetWeatherPresetWeightsBySeason(Season currentSeason)
|
||||
{
|
||||
return WeatherConfig.Weather.WeatherPresetWeight.TryGetValue(currentSeason.ToString(), out var weights)
|
||||
? weights
|
||||
: WeatherConfig.Weather.WeatherPresetWeight.GetValueOrDefault("default")!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Weather"/> object that adheres to the chosen preset
|
||||
/// </summary>
|
||||
/// <param name="chosenPreset">The weather preset chosen to generate</param>
|
||||
/// <param name="timestamp">OPTIONAL - generate the weather object with a specific time instead of now</param>
|
||||
/// <returns>A generated <see cref="Weather"/> object</returns>
|
||||
protected Weather GenerateWeatherByPreset(WeatherPreset chosenPreset, long? timestamp)
|
||||
{
|
||||
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");
|
||||
|
||||
generator = weatherGenerators.FirstOrDefault(gen => gen.CanHandle(WeatherPreset.SUNNY));
|
||||
}
|
||||
|
||||
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)
|
||||
/// <summary>
|
||||
/// Get the weather preset weights based on passed in preset, get defaults if preset not found in config
|
||||
/// </summary>
|
||||
/// <param name="weatherPreset">Desired preset</param>
|
||||
/// <returns>PresetWeights</returns>
|
||||
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"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,7 +130,7 @@ public class WeatherGenerator(
|
||||
/// <param name="weather"> What season Tarkov is currently in </param>
|
||||
/// <param name="inRaidTimestamp"> What time is the raid running at </param>
|
||||
/// <returns> Timestamp </returns>
|
||||
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 @@ public class WeatherGenerator(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ public record SeasonDateTimes
|
||||
|
||||
public record WeatherValues
|
||||
{
|
||||
[JsonPropertyName("seasonValues")]
|
||||
public Dictionary<string, SeasonalValues>? SeasonValues { get; set; }
|
||||
[JsonPropertyName("presetWeights")]
|
||||
public Dictionary<string, PresetWeights>? PresetWeights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many hours to generate weather data into the future
|
||||
@@ -64,30 +64,40 @@ public record WeatherValues
|
||||
/// </summary>
|
||||
[JsonPropertyName("timePeriod")]
|
||||
public WeatherSettings<int>? TimePeriod { get; set; }
|
||||
|
||||
[JsonPropertyName("weatherPresetWeight")]
|
||||
public Dictionary<string, Dictionary<WeatherPreset, double>> WeatherPresetWeight { get; set; }
|
||||
}
|
||||
|
||||
public record SeasonalValues
|
||||
public enum WeatherPreset
|
||||
{
|
||||
SUNNY = 1,
|
||||
RAINY = 2,
|
||||
CLOUDY = 3,
|
||||
}
|
||||
|
||||
public record PresetWeights
|
||||
{
|
||||
[JsonPropertyName("clouds")]
|
||||
public WeatherSettings<double>? Clouds { get; set; }
|
||||
public Dictionary<string, double> Clouds { get; set; }
|
||||
|
||||
[JsonPropertyName("windSpeed")]
|
||||
public WeatherSettings<double>? WindSpeed { get; set; }
|
||||
public Dictionary<string, double>? WindSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("windDirection")]
|
||||
public WeatherSettings<WindDirection>? WindDirection { get; set; }
|
||||
public Dictionary<WindDirection, double>? WindDirection { get; set; }
|
||||
|
||||
[JsonPropertyName("windGustiness")]
|
||||
public MinMax<double>? WindGustiness { get; set; }
|
||||
|
||||
[JsonPropertyName("rain")]
|
||||
public WeatherSettings<double>? Rain { get; set; }
|
||||
public Dictionary<string, double>? Rain { get; set; }
|
||||
|
||||
[JsonPropertyName("rainIntensity")]
|
||||
public MinMax<double>? RainIntensity { get; set; }
|
||||
|
||||
[JsonPropertyName("fog")]
|
||||
public WeatherSettings<double>? Fog { get; set; }
|
||||
public Dictionary<string, double>? Fog { get; set; }
|
||||
|
||||
[JsonPropertyName("temp")]
|
||||
public TempDayNight? Temp { get; set; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public class PostDbLoadService(
|
||||
AddCustomItemPresetsToGlobals();
|
||||
|
||||
var currentSeason = seasonalEventService.GetActiveWeatherSeason();
|
||||
raidWeatherService.GenerateWeather(currentSeason);
|
||||
raidWeatherService.GenerateFutureWeatherAndCache(currentSeason);
|
||||
|
||||
if (BotConfig.WeeklyBoss.Enabled)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using SPTarkov.Server.Core.Models.Enums;
|
||||
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<WeatherConfig>();
|
||||
@@ -24,7 +26,7 @@ public class RaidWeatherService(
|
||||
/// <summary>
|
||||
/// Generate 24 hours of weather data starting from midnight today
|
||||
/// </summary>
|
||||
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 class RaidWeatherService(
|
||||
|
||||
// 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 @@ public class RaidWeatherService(
|
||||
var result = WeatherForecast.Where(weather => weather.Timestamp >= timeUtil.GetTimeStamp());
|
||||
if (!result.Any())
|
||||
{
|
||||
GenerateWeather(currentSeason);
|
||||
GenerateFutureWeatherAndCache(currentSeason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user