From 559a5b237bfb500b747b4fa9c971d534198b6f62 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 18 May 2026 13:09:48 -0700 Subject: [PATCH] Add sprain movement placeholder --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 5 ++- Scripts/verify_sprain_movement_placeholder.py | 41 +++++++++++++++++++ Source/AgrarianGame/AgrarianDebugHUD.cpp | 2 + Source/AgrarianGame/AgrarianGameCharacter.cpp | 6 ++- .../AgrarianGamePlayerController.cpp | 3 +- .../AgrarianSurvivalComponent.cpp | 32 +++++++++++++++ .../AgrarianGame/AgrarianSurvivalComponent.h | 9 ++++ Source/AgrarianGame/AgrarianTypes.h | 3 ++ 8 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 Scripts/verify_sprain_movement_placeholder.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 36b3a62..12ccea1 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -645,7 +645,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe `BleedingSeverity`, applies lightweight health/exhaustion pressure while bleeding, adds bleeding from generic injuries, exposes add/reduce hooks, and shows bleeding in debug HUD and console survival output. -- [ ] Add sprain or movement penalty placeholder. +- [x] Add sprain or movement penalty placeholder. Survival now tracks replicated + `SprainSeverity`, generic injuries can seed sprain severity, active sprains + add light exhaustion pressure, movement speed includes an explicit sprain + multiplier, and debug HUD/console output display sprain state. - [x] Add cold exposure damage. - [x] Add starvation damage. - [x] Add dehydration damage. diff --git a/Scripts/verify_sprain_movement_placeholder.py b/Scripts/verify_sprain_movement_placeholder.py new file mode 100644 index 0000000..2db0e24 --- /dev/null +++ b/Scripts/verify_sprain_movement_placeholder.py @@ -0,0 +1,41 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def require(path: Path, snippet: str) -> None: + text = path.read_text(encoding="utf-8") + if snippet not in text: + raise SystemExit(f"{path.relative_to(ROOT)} missing {snippet!r}") + + +def main() -> None: + types = ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h" + survival_h = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h" + survival_cpp = ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp" + character_cpp = ROOT / "Source" / "AgrarianGame" / "AgrarianGameCharacter.cpp" + hud = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp" + controller = ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp" + roadmap = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md" + + require(types, "float SprainSeverity = 0.0f;") + require(survival_h, "float SprainExhaustionPerSecond = 0.03f;") + require(survival_h, "void AddSprain(float Severity);") + require(survival_h, "void ReduceSprain(float Amount);") + require(survival_cpp, "Survival.SprainSeverity += PositiveSeverity * 0.20f;") + require(survival_cpp, "Survival.Exhaustion += SprainExhaustionPerSecond * SprainRatio * DeltaTime;") + require(survival_cpp, "void UAgrarianSurvivalComponent::AddSprain") + require(survival_cpp, "void UAgrarianSurvivalComponent::ReduceSprain") + require(survival_cpp, "Survival.SprainSeverity = FMath::Clamp(Survival.SprainSeverity, 0.0f, 100.0f);") + require(character_cpp, "const float SprainMultiplier = FMath::GetMappedRangeValueClamped") + require(character_cpp, "InjuryMultiplier * SprainMultiplier * SicknessMultiplier") + require(hud, "Sprain") + require(controller, "Sprain %.1f") + require(roadmap, "[x] Add sprain or movement penalty placeholder.") + + print("PASS: sprain movement placeholder is present.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianDebugHUD.cpp b/Source/AgrarianGame/AgrarianDebugHUD.cpp index 97b0280..03f9a13 100644 --- a/Source/AgrarianGame/AgrarianDebugHUD.cpp +++ b/Source/AgrarianGame/AgrarianDebugHUD.cpp @@ -110,6 +110,7 @@ void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* Surv 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("Bleeding %3.0f"), Survival.BleedingSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.BleedingSeverity, true)); + DrawScaledLine(FString::Printf(TEXT("Sprain %3.0f"), Survival.SprainSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SprainSeverity, true)); DrawScaledLine(FString::Printf(TEXT("Sickness %3.0f"), Survival.SicknessSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SicknessSeverity, true)); } @@ -337,6 +338,7 @@ void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalC DrawLine(FString::Printf(TEXT("Expose: x%.2f %+3.1f C"), SurvivalComponent->CurrentWeatherExposureMultiplier, SurvivalComponent->CurrentWeatherTemperatureOffsetC), X, Y); DrawLine(FString::Printf(TEXT("Injury: %.0f"), Survival.InjurySeverity), X, Y); DrawLine(FString::Printf(TEXT("Bleed: %.0f"), Survival.BleedingSeverity), X, Y); + DrawLine(FString::Printf(TEXT("Sprain: %.0f"), Survival.SprainSeverity), X, Y); DrawLine(FString::Printf(TEXT("Sick: %.0f"), Survival.SicknessSeverity), X, Y); const FAgrarianCareHistorySnapshot& Care = SurvivalComponent->CareHistory; DrawLine(FString::Printf(TEXT("Care N/S/T: %.2f %.2f %.2f"), Care.NutritionQuality, Care.SleepQuality, Care.TreatmentQuality), X, Y, FColor::Silver); diff --git a/Source/AgrarianGame/AgrarianGameCharacter.cpp b/Source/AgrarianGame/AgrarianGameCharacter.cpp index 8188c97..13bd114 100644 --- a/Source/AgrarianGame/AgrarianGameCharacter.cpp +++ b/Source/AgrarianGame/AgrarianGameCharacter.cpp @@ -420,6 +420,10 @@ float AAgrarianGameCharacter::CalculateSurvivalMovementMultiplier() const FVector2D(0.0f, 100.0f), FVector2D(1.0f, 0.5f), Survival.InjurySeverity); + const float SprainMultiplier = FMath::GetMappedRangeValueClamped( + FVector2D(0.0f, 100.0f), + FVector2D(1.0f, 0.45f), + Survival.SprainSeverity); const float SicknessMultiplier = FMath::GetMappedRangeValueClamped( FVector2D(0.0f, 100.0f), FVector2D(1.0f, 0.7f), @@ -429,7 +433,7 @@ float AAgrarianGameCharacter::CalculateSurvivalMovementMultiplier() const FVector2D(1.0f, 0.55f), Survival.Exhaustion); - return HungerMultiplier * ThirstMultiplier * InjuryMultiplier * SicknessMultiplier * ExhaustionMultiplier; + return HungerMultiplier * ThirstMultiplier * InjuryMultiplier * SprainMultiplier * SicknessMultiplier * ExhaustionMultiplier; } float AAgrarianGameCharacter::CalculateCarryWeightMovementMultiplier() const diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.cpp b/Source/AgrarianGame/AgrarianGamePlayerController.cpp index c184f86..2857785 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.cpp +++ b/Source/AgrarianGame/AgrarianGamePlayerController.cpp @@ -147,7 +147,7 @@ void AAgrarianGamePlayerController::AgrarianSurvival() const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival; ClientMessage(FString::Printf( - TEXT("Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sickness %.1f"), + TEXT("Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sprain %.1f | Sickness %.1f"), Survival.Health, Survival.Stamina, Survival.Exhaustion, @@ -156,6 +156,7 @@ void AAgrarianGamePlayerController::AgrarianSurvival() Survival.BodyTemperature, Survival.InjurySeverity, Survival.BleedingSeverity, + Survival.SprainSeverity, Survival.SicknessSeverity)); } diff --git a/Source/AgrarianGame/AgrarianSurvivalComponent.cpp b/Source/AgrarianGame/AgrarianSurvivalComponent.cpp index 2c29178..17c1cb7 100644 --- a/Source/AgrarianGame/AgrarianSurvivalComponent.cpp +++ b/Source/AgrarianGame/AgrarianSurvivalComponent.cpp @@ -73,6 +73,13 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT CareHistory.InjuryBurden += BleedingRatio * 0.001f * DeltaTime; } + if (Survival.SprainSeverity > 0.0f) + { + const float SprainRatio = Survival.SprainSeverity / 100.0f; + Survival.Exhaustion += SprainExhaustionPerSecond * SprainRatio * DeltaTime; + CareHistory.InjuryBurden += SprainRatio * 0.0005f * DeltaTime; + } + if (const UWorld* World = GetWorld()) { if (const AAgrarianGameState* AgrarianGameState = World->GetGameState()) @@ -181,6 +188,7 @@ void UAgrarianSurvivalComponent::AddInjury(float Severity) const float PositiveSeverity = FMath::Max(0.0f, Severity); Survival.InjurySeverity += PositiveSeverity; Survival.BleedingSeverity += PositiveSeverity * 0.35f; + Survival.SprainSeverity += PositiveSeverity * 0.20f; CareHistory.InjuryBurden += PositiveSeverity / 100.0f; Survival.Health -= PositiveSeverity * 5.0f; ClampSurvival(); @@ -222,6 +230,29 @@ void UAgrarianSurvivalComponent::ReduceBleeding(float Amount) } } +void UAgrarianSurvivalComponent::AddSprain(float Severity) +{ + if (GetOwner() && GetOwner()->HasAuthority()) + { + const float PositiveSeverity = FMath::Max(0.0f, Severity); + Survival.SprainSeverity += PositiveSeverity; + CareHistory.InjuryBurden += PositiveSeverity / 200.0f; + ClampSurvival(); + ClampCareHistory(); + BroadcastSurvivalChanged(); + } +} + +void UAgrarianSurvivalComponent::ReduceSprain(float Amount) +{ + if (GetOwner() && GetOwner()->HasAuthority()) + { + Survival.SprainSeverity -= FMath::Max(0.0f, Amount); + ClampSurvival(); + BroadcastSurvivalChanged(); + } +} + void UAgrarianSurvivalComponent::AddSickness(float Severity) { if (GetOwner() && GetOwner()->HasAuthority()) @@ -396,6 +427,7 @@ void UAgrarianSurvivalComponent::ClampSurvival() Survival.BodyTemperature = FMath::Clamp(Survival.BodyTemperature, 30.0f, 42.0f); Survival.InjurySeverity = FMath::Clamp(Survival.InjurySeverity, 0.0f, 100.0f); Survival.BleedingSeverity = FMath::Clamp(Survival.BleedingSeverity, 0.0f, 100.0f); + Survival.SprainSeverity = FMath::Clamp(Survival.SprainSeverity, 0.0f, 100.0f); Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f); } diff --git a/Source/AgrarianGame/AgrarianSurvivalComponent.h b/Source/AgrarianGame/AgrarianSurvivalComponent.h index 0febe06..4496c69 100644 --- a/Source/AgrarianGame/AgrarianSurvivalComponent.h +++ b/Source/AgrarianGame/AgrarianSurvivalComponent.h @@ -78,6 +78,9 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) float BleedingExhaustionPerSecond = 0.04f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0")) + float SprainExhaustionPerSecond = 0.03f; + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") bool IsAlive() const; @@ -108,6 +111,12 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") void ReduceBleeding(float Amount); + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") + void AddSprain(float Severity); + + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") + void ReduceSprain(float Amount); + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") void AddSickness(float Severity); diff --git a/Source/AgrarianGame/AgrarianTypes.h b/Source/AgrarianGame/AgrarianTypes.h index 785ede8..6559ac1 100644 --- a/Source/AgrarianGame/AgrarianTypes.h +++ b/Source/AgrarianGame/AgrarianTypes.h @@ -357,6 +357,9 @@ struct FAgrarianSurvivalSnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") float BleedingSeverity = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") + float SprainSeverity = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival") float SicknessSeverity = 0.0f; };