From 89af9a7aaeb683c4f95c20550c9300c0daf54627 Mon Sep 17 00:00:00 2001 From: C1XTZ Date: Fri, 22 May 2026 21:38:05 +0200 Subject: [PATCH 1/4] RandomWeatherPlugin: startup weather fix --- RandomWeatherPlugin/RandomWeather.cs | 4 +++- RandomWeatherPlugin/RandomWeatherConfiguration.cs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RandomWeatherPlugin/RandomWeather.cs b/RandomWeatherPlugin/RandomWeather.cs index 9d4e31bd..db297b80 100644 --- a/RandomWeatherPlugin/RandomWeather.cs +++ b/RandomWeatherPlugin/RandomWeather.cs @@ -119,13 +119,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); diff --git a/RandomWeatherPlugin/RandomWeatherConfiguration.cs b/RandomWeatherPlugin/RandomWeatherConfiguration.cs index b4025d3e..c5bbab7a 100644 --- a/RandomWeatherPlugin/RandomWeatherConfiguration.cs +++ b/RandomWeatherPlugin/RandomWeatherConfiguration.cs @@ -21,6 +21,9 @@ public class RandomWeatherConfiguration : IValidateConfiguration> 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")] From 9e9d920d5d3a56e60478372bf0b19a9a20f57fbc Mon Sep 17 00:00:00 2001 From: C1XTZ Date: Thu, 25 Jun 2026 23:17:56 +0200 Subject: [PATCH 2/4] fix unbounded growth in RecalculateWeights --- RandomWeatherPlugin/RandomWeather.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/RandomWeatherPlugin/RandomWeather.cs b/RandomWeatherPlugin/RandomWeather.cs index db297b80..c8842172 100644 --- a/RandomWeatherPlugin/RandomWeather.cs +++ b/RandomWeatherPlugin/RandomWeather.cs @@ -47,7 +47,7 @@ public RandomWeather(RandomWeatherConfiguration configuration, WeatherManager we RainWater = last.RainWater, TrackGrip = last.TrackGrip }); - + RecalculateWeights(_configuration.WeatherTransitions[next.WeatherFxType]); } else if (_configuration.Mode == RandomWeatherMode.Default) @@ -62,7 +62,10 @@ public RandomWeather(RandomWeatherConfiguration configuration, WeatherManager we } private void RecalculateWeights(Dictionary input) + private void RecalculateWeights(Dictionary input) { + _weathers.Clear(); + float weightSum = input .Select(w => w.Value) .Sum(); @@ -138,7 +141,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, @@ -154,7 +157,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) RainWater = last.RainWater, TrackGrip = last.TrackGrip }); - + if (_configuration.Mode == RandomWeatherMode.TransitionTable) RecalculateWeights(_configuration.WeatherTransitions[next]); } From b4ee6e904bf5d30bc80577f2d117bf85df2afe91 Mon Sep 17 00:00:00 2001 From: C1XTZ Date: Thu, 25 Jun 2026 23:30:29 +0200 Subject: [PATCH 3/4] more robust config validation --- RandomWeatherPlugin/RandomWeather.cs | 11 +---- .../RandomWeatherConfigurationValidator.cs | 40 +++++++++++++++++-- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/RandomWeatherPlugin/RandomWeather.cs b/RandomWeatherPlugin/RandomWeather.cs index c8842172..0753f840 100644 --- a/RandomWeatherPlugin/RandomWeather.cs +++ b/RandomWeatherPlugin/RandomWeather.cs @@ -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) @@ -52,16 +49,10 @@ public RandomWeather(RandomWeatherConfiguration configuration, WeatherManager we } 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 input) private void RecalculateWeights(Dictionary input) { _weathers.Clear(); @@ -73,7 +64,7 @@ private void RecalculateWeights(Dictionary input) 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 diff --git a/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs b/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs index c74c501a..10976fd7 100644 --- a/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs +++ b/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs @@ -1,4 +1,5 @@ using FluentValidation; +using AssettoServer.Shared.Weather; namespace RandomWeatherPlugin; @@ -6,11 +7,42 @@ public class RandomWeatherConfigurationValidator : AbstractValidator 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) + .NotNull() + .DependentRules(() => + { + RuleFor(cfg => cfg.WeatherTransitions) + .NotEmpty(); + RuleForEach(cfg => cfg.WeatherTransitions) + .Must(x => x.Value.Values.Any(v => v > 0)) + .WithMessage("Each 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) + .NotNull() + .DependentRules(() => + { + 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); }); + RuleFor(cfg => cfg.WeatherWeights) + .Must(ww => !ww.ContainsKey(WeatherFxType.None) || ww[WeatherFxType.None] <= 0) + .WithMessage("WeatherFX Type \"None\" cannot be used as a weather weight"); + }); + }); } } From d4a17cc0e4ba362840eca4b479fe1332a2b3feb8 Mon Sep 17 00:00:00 2001 From: C1XTZ Date: Sat, 27 Jun 2026 23:44:39 +0200 Subject: [PATCH 4/4] notnull -> not empty, missing wfx none check --- .../RandomWeatherConfigurationValidator.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs b/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs index 10976fd7..fd15c159 100644 --- a/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs +++ b/RandomWeatherPlugin/RandomWeatherConfigurationValidator.cs @@ -12,14 +12,18 @@ public RandomWeatherConfigurationValidator() When(cfg => cfg.Mode == RandomWeatherMode.TransitionTable, () => { RuleFor(cfg => cfg.WeatherTransitions) - .NotNull() + .NotEmpty() .DependentRules(() => { RuleFor(cfg => cfg.WeatherTransitions) - .NotEmpty(); + .Must(wt => !wt.ContainsKey(WeatherFxType.None)) + .WithMessage("WeatherFX Type \"None\" cannot be used as a weather"); RuleForEach(cfg => cfg.WeatherTransitions) - .Must(x => x.Value.Values.Any(v => v > 0)) - .WithMessage("Each WeatherTransitions entry must contain at least one destination weather with a weight greater than 0"); + .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 => { @@ -31,17 +35,17 @@ public RandomWeatherConfigurationValidator() When(cfg => cfg.Mode == RandomWeatherMode.Default, () => { RuleFor(cfg => cfg.WeatherWeights) - .NotNull() + .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); }); - RuleFor(cfg => cfg.WeatherWeights) - .Must(ww => !ww.ContainsKey(WeatherFxType.None) || ww[WeatherFxType.None] <= 0) - .WithMessage("WeatherFX Type \"None\" cannot be used as a weather weight"); }); }); }