Add deterministic weather fallback

This commit is contained in:
2026-05-16 00:01:20 -07:00
parent ff6fc61af3
commit 3740eb32bf
5 changed files with 141 additions and 2 deletions
@@ -24,7 +24,7 @@ bool UAgrarianWeatherProviderSubsystem::RequestWeatherForActiveGameState()
bool UAgrarianWeatherProviderSubsystem::RequestWeatherForTile(FName TileId, float Latitude, float Longitude)
{
if (!bEnableLiveWeatherRequests || TileId == NAME_None)
if (TileId == NAME_None)
{
return false;
}
@@ -40,6 +40,10 @@ bool UAgrarianWeatherProviderSubsystem::RequestWeatherForTile(FName TileId, floa
{
return true;
}
if (!bEnableLiveWeatherRequests)
{
return ApplyDeterministicFallbackWeather(TileId, Latitude, Longitude, GameState);
}
const FString Url = BuildOpenMeteoForecastUrl(TileId, Latitude, Longitude);
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
@@ -163,6 +167,57 @@ void UAgrarianWeatherProviderSubsystem::ClearWeatherSnapshotCache()
ServerWeatherSnapshotCache.Empty();
}
bool UAgrarianWeatherProviderSubsystem::ApplyDeterministicFallbackWeather(FName TileId, float Latitude, float Longitude, AAgrarianGameState* GameState)
{
if (!bEnableDeterministicFallbackWeather || !GameState || !GameState->HasAuthority() || TileId == NAME_None)
{
return false;
}
const FAgrarianWeatherProviderSnapshot Snapshot = BuildDeterministicFallbackSnapshot(
TileId,
Latitude,
Longitude,
GameState->ActiveDayOfYear,
GameState->WorldHours);
LastSnapshot = Snapshot;
CacheSnapshot(Snapshot, WeatherSnapshotCacheTtlSeconds);
return ApplySnapshotToGameState(Snapshot, GameState);
}
FAgrarianWeatherProviderSnapshot UAgrarianWeatherProviderSubsystem::BuildDeterministicFallbackSnapshot(FName TileId, float Latitude, float Longitude, int32 DayOfYear, float HourOfDay) const
{
const int32 SafeDay = FMath::Clamp(DayOfYear, 1, 366);
const float SeasonalRadians = ((static_cast<float>(SafeDay) - 172.0f) / 366.0f) * 2.0f * PI;
const float LatitudeSeasonScale = FMath::Clamp(FMath::Abs(Latitude) / 60.0f, 0.0f, 1.0f);
const float SeasonalTemperatureC = 12.0f + (FMath::Cos(SeasonalRadians) * 10.0f * LatitudeSeasonScale);
const float DailySwingC = FMath::Lerp(4.0f, 10.0f, LatitudeSeasonScale);
const float Noise = GetDeterministicWeatherNoise(TileId, SafeDay, 11);
const float StormNoise = GetDeterministicWeatherNoise(TileId, SafeDay, 23);
const float CloudNoise = GetDeterministicWeatherNoise(TileId, SafeDay, 37);
const float WindNoise = GetDeterministicWeatherNoise(TileId, SafeDay, 53);
FAgrarianWeatherProviderSnapshot Snapshot;
Snapshot.TileId = TileId;
Snapshot.Latitude = FMath::Clamp(Latitude, -90.0f, 90.0f);
Snapshot.Longitude = FMath::Clamp(Longitude, -180.0f, 180.0f);
Snapshot.Provider = TEXT("deterministic-fallback");
Snapshot.ProviderTimestamp = FString::Printf(TEXT("day-%03d-hour-%02d"), SafeDay, FMath::FloorToInt(FMath::Clamp(HourOfDay, 0.0f, 24.0f)));
Snapshot.DailyLowTemperatureC = SeasonalTemperatureC - DailySwingC + FMath::Lerp(-2.0f, 2.0f, Noise);
Snapshot.DailyHighTemperatureC = SeasonalTemperatureC + DailySwingC + FMath::Lerp(-2.0f, 2.0f, Noise);
Snapshot.CurrentTemperatureC = (Snapshot.DailyLowTemperatureC + Snapshot.DailyHighTemperatureC) * 0.5f;
Snapshot.CloudCoverPercent = FMath::Clamp(CloudNoise * 100.0f, 0.0f, 100.0f);
Snapshot.RelativeHumidityPercent = FMath::Clamp(45.0f + (Snapshot.CloudCoverPercent * 0.35f) + FMath::Lerp(-10.0f, 10.0f, Noise), 10.0f, 100.0f);
Snapshot.WindSpeedKmh = FMath::Clamp(5.0f + (WindNoise * 35.0f), 0.0f, 75.0f);
Snapshot.PressureMslHpa = FMath::Clamp(1018.0f - (Snapshot.CloudCoverPercent * 0.12f) - (Snapshot.WindSpeedKmh * 0.08f), 960.0f, 1040.0f);
Snapshot.PrecipitationMm = StormNoise > 0.72f ? FMath::Lerp(0.1f, 8.0f, StormNoise) : 0.0f;
Snapshot.VisibilityMeters = FMath::Clamp(10000.0f - (Snapshot.CloudCoverPercent * 30.0f) - (Snapshot.PrecipitationMm * 300.0f), 250.0f, 10000.0f);
Snapshot.WeatherCode = Snapshot.PrecipitationMm > 0.0f ? 61 : 0;
Snapshot.MappedWeather = MapOpenMeteoWeatherCode(Snapshot.WeatherCode, Snapshot.PrecipitationMm, Snapshot.WindSpeedKmh);
Snapshot.bIsValid = true;
return Snapshot;
}
EAgrarianWeatherType UAgrarianWeatherProviderSubsystem::MapOpenMeteoWeatherCode(int32 WeatherCode, float PrecipitationMm, float WindSpeedKmh)
{
if (WeatherCode >= 95 || WindSpeedKmh >= 55.0f)
@@ -184,6 +239,12 @@ EAgrarianWeatherType UAgrarianWeatherProviderSubsystem::MapOpenMeteoWeatherCode(
return EAgrarianWeatherType::Clear;
}
float UAgrarianWeatherProviderSubsystem::GetDeterministicWeatherNoise(FName TileId, int32 DayOfYear, int32 Salt) const
{
const FString SeedString = FString::Printf(TEXT("%s:%d:%d"), *TileId.ToString(), DayOfYear, Salt);
return static_cast<float>(GetTypeHash(SeedString) % 10000) / 9999.0f;
}
FString UAgrarianWeatherProviderSubsystem::MakeCacheKey(FName TileId, const FString& Provider) const
{
return FString::Printf(TEXT("%s:%s"), *Provider.ToLower(), *TileId.ToString());
@@ -207,12 +268,16 @@ void UAgrarianWeatherProviderSubsystem::OnOpenMeteoResponse(FHttpRequestPtr Requ
{
if (!bWasSuccessful || !Response.IsValid() || Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
{
UWorld* World = GetWorld();
ApplyDeterministicFallbackWeather(TileId, Latitude, Longitude, World ? World->GetGameState<AAgrarianGameState>() : nullptr);
return;
}
FAgrarianWeatherProviderSnapshot Snapshot;
if (!ParseOpenMeteoForecast(Response->GetContentAsString(), TileId, Latitude, Longitude, Snapshot))
{
UWorld* World = GetWorld();
ApplyDeterministicFallbackWeather(TileId, Latitude, Longitude, World ? World->GetGameState<AAgrarianGameState>() : nullptr);
return;
}
@@ -265,12 +330,16 @@ void UAgrarianWeatherProviderSubsystem::OnNoaaNwsGridDataResponse(FHttpRequestPt
{
if (!bWasSuccessful || !Response.IsValid() || Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
{
UWorld* World = GetWorld();
ApplyDeterministicFallbackWeather(TileId, Latitude, Longitude, World ? World->GetGameState<AAgrarianGameState>() : nullptr);
return;
}
FAgrarianWeatherProviderSnapshot Snapshot;
if (!ParseNoaaNwsGridData(Response->GetContentAsString(), TileId, Latitude, Longitude, Snapshot))
{
UWorld* World = GetWorld();
ApplyDeterministicFallbackWeather(TileId, Latitude, Longitude, World ? World->GetGameState<AAgrarianGameState>() : nullptr);
return;
}