Connect shelter to weather protection
This commit is contained in:
@@ -434,7 +434,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
- [x] Store weather source, provider timestamp, tile coordinate, and applied in-game weather state for debugging and persistence. Added tile ID/coordinate fields to mapped weather inputs, a replicated `FAgrarianWeatherDebugSnapshot` on game state, provider snapshot mapping into the debug path, and save fields for mapped inputs plus applied weather debug state.
|
||||
- [x] Add weather save/load support. Added `LoadCurrentWorld` as the unified persistence load path, restored weather/time before players and world actors, updated the admin load command to use the combined path, and extended the persistence smoke test to prove provider-backed weather metadata survives save/load.
|
||||
- [x] Connect weather to body temperature.
|
||||
- [~] Connect shelter to weather protection.
|
||||
- [x] Connect shelter to weather protection. Survival now calculates the best overlapping shelter protection volume, replicates current weather protection, reduces ambient exposure and cold damage by shelter coverage, trends care-history shelter quality toward active protection, and shows shelter protection on the dev HUD.
|
||||
- [ ] Add first-pass sky and lighting.
|
||||
- [ ] Add audio cues for weather.
|
||||
|
||||
|
||||
@@ -147,6 +147,14 @@ clients to call public weather APIs directly. This keeps real-world temperature
|
||||
and weather tied to the represented map tile while preserving a deterministic
|
||||
fallback if an external provider is unavailable.
|
||||
|
||||
Primitive shelters expose a replicated protection volume and
|
||||
`WeatherProtection` rating. Server-side survival ticks calculate the best
|
||||
overlapping shelter protection for each character, replicate the current
|
||||
protection value, reduce ambient weather exposure and cold damage by that
|
||||
percentage, and trend the care-history shelter quality field toward the active
|
||||
protection level. The dev HUD shows current shelter protection so weather
|
||||
pressure can be tuned during MVP tests.
|
||||
|
||||
The first real-weather adapter is `UAgrarianWeatherProviderSubsystem`. It uses
|
||||
Open-Meteo forecast requests keyed by tile center latitude/longitude, parses the
|
||||
current temperature, daily low/high, precipitation, wind, humidity, cloud cover,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SURVIVAL_H = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h"
|
||||
SURVIVAL_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp"
|
||||
SHELTER_H = ROOT / "Source" / "AgrarianGame" / "AgrarianShelterActor.h"
|
||||
SHELTER_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianShelterActor.cpp"
|
||||
DEBUG_HUD_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp"
|
||||
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
|
||||
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||
|
||||
|
||||
EXPECTED = {
|
||||
SHELTER_H: [
|
||||
"TObjectPtr<UBoxComponent> ProtectionVolume;",
|
||||
"float WeatherProtection = 0.65f;",
|
||||
],
|
||||
SHELTER_CPP: [
|
||||
"ProtectionVolume->SetBoxExtent",
|
||||
"ProtectionVolume->SetCollisionProfileName(TEXT(\"OverlapAllDynamic\"))",
|
||||
],
|
||||
SURVIVAL_H: [
|
||||
"float CurrentWeatherProtection = 0.0f;",
|
||||
"float CalculateCurrentWeatherProtection() const;",
|
||||
],
|
||||
SURVIVAL_CPP: [
|
||||
"#include \"AgrarianShelterActor.h\"",
|
||||
"DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherProtection);",
|
||||
"CurrentWeatherProtection = CalculateCurrentWeatherProtection();",
|
||||
"CareHistory.ShelterQuality = FMath::FInterpTo",
|
||||
"ExposureProtectionMultiplier",
|
||||
"ColdDamagePerMinute * Minutes * (1.0f - FMath::Clamp(CurrentWeatherProtection",
|
||||
"Owner->GetOverlappingActors(OverlappingShelterActors, AAgrarianShelterActor::StaticClass());",
|
||||
"Shelter->ProtectionVolume->IsOverlappingActor(Owner)",
|
||||
],
|
||||
DEBUG_HUD_CPP: [
|
||||
"Shelter %3.0f%%",
|
||||
"Shelter: %.0f%%",
|
||||
],
|
||||
TDD: [
|
||||
"Primitive shelters expose a replicated protection volume",
|
||||
"reduce ambient weather exposure and cold damage",
|
||||
],
|
||||
ROADMAP: [
|
||||
"[x] Connect shelter to weather protection.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
missing = []
|
||||
for path, snippets in EXPECTED.items():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for snippet in snippets:
|
||||
if snippet not in text:
|
||||
missing.append(f"{path.relative_to(ROOT)}: {snippet}")
|
||||
if missing:
|
||||
raise RuntimeError("Shelter weather protection verification failed: " + "; ".join(missing))
|
||||
print("Agrarian shelter weather protection verification complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -102,6 +102,7 @@ void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* Surv
|
||||
DrawScaledLine(FString::Printf(TEXT("Food %3.0f"), Survival.Hunger), X, Y, CriticalStatsTextScale, StatusColor(Survival.Hunger));
|
||||
DrawScaledLine(FString::Printf(TEXT("Water %3.0f"), Survival.Thirst), X, Y, CriticalStatsTextScale, StatusColor(Survival.Thirst));
|
||||
DrawScaledLine(FString::Printf(TEXT("Temp %4.1f C"), Survival.BodyTemperature), X, Y, CriticalStatsTextScale, Survival.BodyTemperature < 35.0f ? CriticalColor : StableColor);
|
||||
DrawScaledLine(FString::Printf(TEXT("Shelter %3.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y, CriticalStatsTextScale, SurvivalComponent->CurrentWeatherProtection > 0.0f ? StableColor : WarningColor);
|
||||
DrawScaledLine(FString::Printf(TEXT("Exhaust %3.0f"), Survival.Exhaustion), X, Y, CriticalStatsTextScale, StatusColor(Survival.Exhaustion, true));
|
||||
DrawScaledLine(FString::Printf(TEXT("Injury %3.0f"), Survival.InjurySeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.InjurySeverity, true));
|
||||
DrawScaledLine(FString::Printf(TEXT("Sickness %3.0f"), Survival.SicknessSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SicknessSeverity, true));
|
||||
@@ -171,6 +172,7 @@ void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalC
|
||||
DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Thirst: %.0f"), Survival.Thirst), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Temp: %.1f C"), Survival.BodyTemperature), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Shelter: %.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Injury: %.0f"), Survival.InjurySeverity), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Sick: %.0f"), Survival.SicknessSeverity), X, Y);
|
||||
const FAgrarianCareHistorySnapshot& Care = SurvivalComponent->CareHistory;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "AgrarianSurvivalComponent.h"
|
||||
#include "AgrarianGameState.h"
|
||||
#include "AgrarianShelterActor.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
@@ -32,6 +34,8 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
|
||||
Survival.Hunger -= HungerDecayPerMinute * Minutes;
|
||||
Survival.Thirst -= ThirstDecayPerMinute * Minutes;
|
||||
Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;
|
||||
CurrentWeatherProtection = CalculateCurrentWeatherProtection();
|
||||
CareHistory.ShelterQuality = FMath::FInterpTo(CareHistory.ShelterQuality, CurrentWeatherProtection, DeltaTime, 0.02f);
|
||||
|
||||
if (Survival.Stamina <= LowStaminaExhaustionThreshold)
|
||||
{
|
||||
@@ -57,7 +61,8 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
|
||||
{
|
||||
if (const AAgrarianGameState* AgrarianGameState = World->GetGameState<AAgrarianGameState>())
|
||||
{
|
||||
const float ExposureDelta = (AgrarianGameState->AmbientTemperatureC - 18.0f) * 0.002f * DeltaTime;
|
||||
const float ExposureProtectionMultiplier = 1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f);
|
||||
const float ExposureDelta = (AgrarianGameState->AmbientTemperatureC - 18.0f) * 0.002f * DeltaTime * ExposureProtectionMultiplier;
|
||||
Survival.BodyTemperature += FMath::Clamp(ExposureDelta, -0.035f, 0.02f);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +79,7 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
|
||||
|
||||
if (Survival.BodyTemperature < 35.0f)
|
||||
{
|
||||
Survival.Health -= ColdDamagePerMinute * Minutes;
|
||||
Survival.Health -= ColdDamagePerMinute * Minutes * (1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
if (Survival.SicknessSeverity >= 60.0f)
|
||||
@@ -92,6 +97,7 @@ void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProp
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
DOREPLIFETIME(UAgrarianSurvivalComponent, Survival);
|
||||
DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);
|
||||
DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherProtection);
|
||||
}
|
||||
|
||||
bool UAgrarianSurvivalComponent::IsAlive() const
|
||||
@@ -229,6 +235,32 @@ void UAgrarianSurvivalComponent::ReduceExhaustion(float Amount)
|
||||
}
|
||||
}
|
||||
|
||||
float UAgrarianSurvivalComponent::CalculateCurrentWeatherProtection() const
|
||||
{
|
||||
const AActor* Owner = GetOwner();
|
||||
if (!Owner)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
TArray<AActor*> OverlappingShelterActors;
|
||||
Owner->GetOverlappingActors(OverlappingShelterActors, AAgrarianShelterActor::StaticClass());
|
||||
|
||||
float BestProtection = 0.0f;
|
||||
for (const AActor* Actor : OverlappingShelterActors)
|
||||
{
|
||||
const AAgrarianShelterActor* Shelter = Cast<AAgrarianShelterActor>(Actor);
|
||||
if (!Shelter || !Shelter->ProtectionVolume || !Shelter->ProtectionVolume->IsOverlappingActor(Owner))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BestProtection = FMath::Max(BestProtection, FMath::Clamp(Shelter->WeatherProtection, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
return BestProtection;
|
||||
}
|
||||
|
||||
void UAgrarianSurvivalComponent::OnRep_Survival()
|
||||
{
|
||||
BroadcastSurvivalChanged();
|
||||
|
||||
@@ -30,6 +30,9 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_CareHistory, Category = "Agrarian|Survival")
|
||||
FAgrarianCareHistorySnapshot CareHistory;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|Survival|Shelter")
|
||||
float CurrentWeatherProtection = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float HungerDecayPerMinute = 0.55f;
|
||||
|
||||
@@ -102,6 +105,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void ReduceExhaustion(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|Survival|Shelter")
|
||||
float CalculateCurrentWeatherProtection() const;
|
||||
|
||||
protected:
|
||||
UFUNCTION()
|
||||
void OnRep_Survival();
|
||||
|
||||
Reference in New Issue
Block a user