// Copyright Pacificao. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "HttpFwd.h" #include "Subsystems/GameInstanceSubsystem.h" #include "AgrarianTypes.h" #include "AgrarianWeatherProviderSubsystem.generated.h" class AAgrarianGameState; class IHttpRequest; class IHttpResponse; USTRUCT(BlueprintType) struct FAgrarianWeatherProviderSnapshot { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FName TileId = NAME_None; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float Latitude = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float Longitude = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FString Provider = TEXT("open-meteo"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FString ProviderTimestamp; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float CurrentTemperatureC = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float DailyLowTemperatureC = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float DailyHighTemperatureC = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float PrecipitationMm = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float WindSpeedKmh = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float CloudCoverPercent = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float RelativeHumidityPercent = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float PressureMslHpa = 0.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float VisibilityMeters = 10000.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") int32 WeatherCode = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") EAgrarianWeatherType MappedWeather = EAgrarianWeatherType::Clear; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") bool bIsValid = false; }; USTRUCT(BlueprintType) struct FAgrarianCachedWeatherSnapshot { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FAgrarianWeatherProviderSnapshot Snapshot; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FDateTime CachedAtUtc = FDateTime::MinValue(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") float TimeToLiveSeconds = 900.0f; bool IsFresh(const FDateTime& NowUtc) const { return Snapshot.bIsValid && CachedAtUtc != FDateTime::MinValue() && (NowUtc - CachedAtUtc).GetTotalSeconds() <= TimeToLiveSeconds; } }; UCLASS() class UAgrarianWeatherProviderSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") bool bEnableLiveWeatherRequests = true; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FString OpenMeteoForecastEndpoint = TEXT("https://api.open-meteo.com/v1/forecast"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") bool bEnableNoaaNwsFallbackForUSTiles = true; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FString NoaaNwsPointsEndpoint = TEXT("https://api.weather.gov/points"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") FString NoaaNwsUserAgent = TEXT("AgrarianGameMVP/0.1"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather", meta = (ClampMin = "60.0")) float WeatherSnapshotCacheTtlSeconds = 900.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Weather") bool bEnableDeterministicFallbackWeather = true; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather") FAgrarianWeatherProviderSnapshot LastSnapshot; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather") FString LastNoaaNwsForecastGridDataUrl; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Weather") TMap ServerWeatherSnapshotCache; UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool RequestWeatherForActiveGameState(); UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool RequestWeatherForTile(FName TileId, float Latitude, float Longitude); UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") FString BuildOpenMeteoForecastUrl(FName TileId, float Latitude, float Longitude) const; UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool RequestNoaaNwsFallbackForTile(FName TileId, float Latitude, float Longitude); UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") bool IsNoaaNwsEligibleCoordinate(float Latitude, float Longitude) const; UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") FString BuildNoaaNwsPointsUrl(float Latitude, float Longitude) const; UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool ApplySnapshotToGameState(const FAgrarianWeatherProviderSnapshot& Snapshot, AAgrarianGameState* GameState) const; UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") FAgrarianMappedWeatherInputs MapSnapshotToAgrarianWeatherInputs(const FAgrarianWeatherProviderSnapshot& Snapshot) const; UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool TryApplyCachedSnapshot(FName TileId, const FString& Provider, AAgrarianGameState* GameState); UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") bool HasFreshCachedSnapshot(FName TileId, const FString& Provider) const; UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") void ClearWeatherSnapshotCache(); UFUNCTION(BlueprintCallable, Category = "Agrarian|Weather") bool ApplyDeterministicFallbackWeather(FName TileId, float Latitude, float Longitude, AAgrarianGameState* GameState); UFUNCTION(BlueprintPure, Category = "Agrarian|Weather") FAgrarianWeatherProviderSnapshot BuildDeterministicFallbackSnapshot(FName TileId, float Latitude, float Longitude, int32 DayOfYear, float HourOfDay) const; static EAgrarianWeatherType MapOpenMeteoWeatherCode(int32 WeatherCode, float PrecipitationMm, float WindSpeedKmh); private: float GetDeterministicWeatherNoise(FName TileId, int32 DayOfYear, int32 Salt) const; FString MakeCacheKey(FName TileId, const FString& Provider) const; void CacheSnapshot(const FAgrarianWeatherProviderSnapshot& Snapshot, float TimeToLiveSeconds); void OnOpenMeteoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude); void OnNoaaNwsPointsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude); void OnNoaaNwsGridDataResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FName TileId, float Latitude, float Longitude); bool ParseOpenMeteoForecast(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const; bool ParseNoaaNwsGridData(const FString& ResponseContent, FName TileId, float Latitude, float Longitude, FAgrarianWeatherProviderSnapshot& OutSnapshot) const; };