Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions RandomWeatherPlugin/RandomWeather.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ public RandomWeather(RandomWeatherConfiguration configuration, WeatherManager we

if (_configuration.Mode == RandomWeatherMode.TransitionTable)
{
if (_configuration.WeatherTransitions.Count == 0)
throw new ConfigurationException("No entries were found in the WeatherTransitions list");

var next = _weatherTypeProvider.GetWeatherType(_configuration.WeatherTransitions.First().Key);
var last = _weatherManager.CurrentWeather;
_weatherManager.SetWeather(new WeatherData(last.Type, next)
Expand All @@ -47,30 +44,27 @@ public RandomWeather(RandomWeatherConfiguration configuration, WeatherManager we
RainWater = last.RainWater,
TrackGrip = last.TrackGrip
});

RecalculateWeights(_configuration.WeatherTransitions[next.WeatherFxType]);
}
else if (_configuration.Mode == RandomWeatherMode.Default)
{
if (_configuration.WeatherWeights.Count == 0)
throw new ConfigurationException("No entries were found in the WeatherWeights list");

_configuration.WeatherWeights[WeatherFxType.None] = 0;

RecalculateWeights(_configuration.WeatherWeights);
}
}

private void RecalculateWeights(Dictionary<WeatherFxType,float> input)
private void RecalculateWeights(Dictionary<WeatherFxType, float> input)
{
_weathers.Clear();

float weightSum = input
.Select(w => w.Value)
.Sum();

float prefixSum = 0.0f;
foreach (var (weather, weight) in input)
{
if (weight > 0)
if (weight > 0 && weather != WeatherFxType.None)
{
prefixSum += weight / weightSum;
_weathers.Add(new WeatherWeight
Expand Down Expand Up @@ -119,13 +113,15 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
int weatherDuration = 1000;
int transitionDuration = 1000;
bool firstRun = true;

while (!stoppingToken.IsCancellationRequested)
{
try
{
weatherDuration = Random.Shared.Next(_configuration.MinWeatherDurationMilliseconds, _configuration.MaxWeatherDurationMilliseconds);
transitionDuration = Random.Shared.Next(_configuration.MinTransitionDurationMilliseconds, _configuration.MaxTransitionDurationMilliseconds);
transitionDuration = (firstRun && _configuration.RandomizeInitialWeather) ? 0 : Random.Shared.Next(_configuration.MinTransitionDurationMilliseconds, _configuration.MaxTransitionDurationMilliseconds);
firstRun = false;

var next = PickRandom();
var nextWeatherType = _weatherTypeProvider.GetWeatherType(next);
Expand All @@ -136,7 +132,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
nextWeatherType.WeatherFxType,
Math.Round(transitionDuration / 1000.0f),
Math.Round(weatherDuration / 60_000.0f, 1));

_weatherManager.SetWeather(new WeatherData(last.Type, nextWeatherType)
{
TransitionDuration = transitionDuration,
Expand All @@ -152,7 +148,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
RainWater = last.RainWater,
TrackGrip = last.TrackGrip
});

if (_configuration.Mode == RandomWeatherMode.TransitionTable)
RecalculateWeights(_configuration.WeatherTransitions[next]);
}
Expand Down
3 changes: 3 additions & 0 deletions RandomWeatherPlugin/RandomWeatherConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class RandomWeatherConfiguration : IValidateConfiguration<RandomWeatherCo
[YamlMember(Description = "Maximum weather transition duration")]
public int MaxTransitionDurationSeconds { get; set; } = 600;

[YamlMember(Description = "If true, the server will start and apply the first random weather instantly, skipping the initial transition from the weather configured in server_cfg.ini")]
public bool RandomizeInitialWeather { get; set; } = false;

[YamlMember(Description = "Weights for weather transition, only listed weathers will be counted\nCheck the reference config to see the structure")]
public Dictionary<WeatherFxType, Dictionary<WeatherFxType, float>> WeatherTransitions { get; init; } = new();
[YamlMember(Description = "Weights for random weather selection, removing a weight or setting it to 0 blacklists a weather\nYou can also use decimals like 0.1")]
Expand Down
44 changes: 40 additions & 4 deletions RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
using FluentValidation;
using AssettoServer.Shared.Weather;

namespace RandomWeatherPlugin;

public class RandomWeatherConfigurationValidator : AbstractValidator<RandomWeatherConfiguration>
{
public RandomWeatherConfigurationValidator()
{
RuleForEach(cfg => cfg.WeatherWeights).ChildRules(ww =>
{
ww.RuleFor(w => w.Value).GreaterThanOrEqualTo(0);
});
RuleFor(cfg => cfg.MinWeatherDurationMinutes).LessThanOrEqualTo(cfg => cfg.MaxWeatherDurationMinutes);
RuleFor(cfg => cfg.MinTransitionDurationSeconds).LessThanOrEqualTo(cfg => cfg.MaxTransitionDurationSeconds);
When(cfg => cfg.Mode == RandomWeatherMode.TransitionTable, () =>
{
RuleFor(cfg => cfg.WeatherTransitions)
.NotEmpty()
.DependentRules(() =>
{
RuleFor(cfg => cfg.WeatherTransitions)
.Must(wt => !wt.ContainsKey(WeatherFxType.None))
.WithMessage("WeatherFX Type \"None\" cannot be used as a weather");
RuleForEach(cfg => cfg.WeatherTransitions)
.Must(x => !x.Value.ContainsKey(WeatherFxType.None) || x.Value[WeatherFxType.None] <= 0)
.WithMessage("WeatherFX Type \"None\" cannot be used as a weather");
RuleForEach(cfg => cfg.WeatherTransitions)
.Must(wt => wt.Value.Values.Any(v => v > 0))
.WithMessage("Every WeatherTransitions entry must contain at least one destination weather with a weight greater than 0");
RuleFor(cfg => cfg.WeatherTransitions)
.Must(wt =>
{
var destinations = wt.Values.SelectMany(d => d.Keys).Distinct();
return destinations.All(d => d == WeatherFxType.None || wt.ContainsKey(d));
}).WithMessage("Every weather that can be transitioned to must also have its own WeatherTransitions section");
});
});
When(cfg => cfg.Mode == RandomWeatherMode.Default, () =>
{
RuleFor(cfg => cfg.WeatherWeights)
.NotEmpty()
.DependentRules(() =>
{
RuleFor(cfg => cfg.WeatherWeights)
.Must(ww => !ww.ContainsKey(WeatherFxType.None) || ww[WeatherFxType.None] <= 0)
.WithMessage("WeatherFX Type \"None\" cannot be used as a weather");
RuleFor(cfg => cfg.WeatherWeights)
.Must(ww => ww.Values.Any(v => v > 0))
.WithMessage("At least one entry in WeatherWeights must have a weight greater than 0");
RuleForEach(cfg => cfg.WeatherWeights)
.ChildRules(ww => { ww.RuleFor(w => w.Value).GreaterThanOrEqualTo(0); });
});
});
}
}