Add survival death state
This commit is contained in:
@@ -656,7 +656,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
|||||||
now the MVP treatment item: item use reduces injury, bleeding, and sprain
|
now the MVP treatment item: item use reduces injury, bleeding, and sprain
|
||||||
severity with a small health bump, using the existing server-authoritative
|
severity with a small health bump, using the existing server-authoritative
|
||||||
`AgrarianUseItem` path.
|
`AgrarianUseItem` path.
|
||||||
- [ ] Add death state.
|
- [x] Add death state. Survival now has replicated `bIsDead` and
|
||||||
|
`LastDeathReason` state, health depletion latches death, ordinary healing
|
||||||
|
cannot revive a dead character, `Revive` provides an explicit respawn/admin
|
||||||
|
hook, and debug HUD/console survival output show alive/dead state.
|
||||||
- [ ] Add respawn rules for MVP.
|
- [ ] Add respawn rules for MVP.
|
||||||
- [ ] Add corpse/backpack placeholder if needed.
|
- [ ] Add corpse/backpack placeholder if needed.
|
||||||
- [ ] Add replicated death feedback.
|
- [ ] Add replicated death feedback.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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"
|
||||||
|
hud = ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp"
|
||||||
|
controller = ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp"
|
||||||
|
roadmap = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||||
|
|
||||||
|
require(types, "bool bIsDead = false;")
|
||||||
|
require(types, "FName LastDeathReason = NAME_None;")
|
||||||
|
require(survival_h, "bool IsDead() const;")
|
||||||
|
require(survival_h, "void MarkDead(FName Reason);")
|
||||||
|
require(survival_h, "void Revive(float HealthAmount = 100.0f);")
|
||||||
|
require(survival_h, "void UpdateDeathState();")
|
||||||
|
require(survival_cpp, "return !Survival.bIsDead && Survival.Health > 0.0f;")
|
||||||
|
require(survival_cpp, "bool UAgrarianSurvivalComponent::IsDead() const")
|
||||||
|
require(survival_cpp, "void UAgrarianSurvivalComponent::MarkDead")
|
||||||
|
require(survival_cpp, "void UAgrarianSurvivalComponent::Revive")
|
||||||
|
require(survival_cpp, "void UAgrarianSurvivalComponent::UpdateDeathState")
|
||||||
|
require(survival_cpp, "Survival.LastDeathReason = FName(TEXT(\"health_depleted\"));")
|
||||||
|
require(controller, "Survival.bIsDead ? TEXT(\"DEAD\") : TEXT(\"ALIVE\")")
|
||||||
|
require(controller, "SurvivalComponent->Revive(100.0f);")
|
||||||
|
require(hud, "State DEAD")
|
||||||
|
require(hud, "State: %s")
|
||||||
|
require(roadmap, "[x] Add death state.")
|
||||||
|
|
||||||
|
print("PASS: death state is present.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -100,6 +100,7 @@ void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* Surv
|
|||||||
};
|
};
|
||||||
|
|
||||||
DrawScaledLine(TEXT("SURVIVAL"), X, Y, CriticalStatsTextScale, HeaderColor);
|
DrawScaledLine(TEXT("SURVIVAL"), X, Y, CriticalStatsTextScale, HeaderColor);
|
||||||
|
DrawScaledLine(Survival.bIsDead ? TEXT("State DEAD") : TEXT("State ALIVE"), X, Y, CriticalStatsTextScale, Survival.bIsDead ? CriticalColor : StableColor);
|
||||||
DrawScaledLine(FString::Printf(TEXT("Health %3.0f"), Survival.Health), X, Y, CriticalStatsTextScale, StatusColor(Survival.Health));
|
DrawScaledLine(FString::Printf(TEXT("Health %3.0f"), Survival.Health), X, Y, CriticalStatsTextScale, StatusColor(Survival.Health));
|
||||||
DrawScaledLine(FString::Printf(TEXT("Stamina %3.0f"), Survival.Stamina), X, Y, CriticalStatsTextScale, StatusColor(Survival.Stamina));
|
DrawScaledLine(FString::Printf(TEXT("Stamina %3.0f"), Survival.Stamina), X, Y, CriticalStatsTextScale, StatusColor(Survival.Stamina));
|
||||||
DrawScaledLine(FString::Printf(TEXT("Food %3.0f"), Survival.Hunger), X, Y, CriticalStatsTextScale, StatusColor(Survival.Hunger));
|
DrawScaledLine(FString::Printf(TEXT("Food %3.0f"), Survival.Hunger), X, Y, CriticalStatsTextScale, StatusColor(Survival.Hunger));
|
||||||
@@ -334,6 +335,7 @@ void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalC
|
|||||||
DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y);
|
DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y);
|
||||||
DrawLine(FString::Printf(TEXT("Thirst: %.0f"), Survival.Thirst), 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("Temp: %.1f C"), Survival.BodyTemperature), X, Y);
|
||||||
|
DrawLine(FString::Printf(TEXT("State: %s"), Survival.bIsDead ? TEXT("DEAD") : TEXT("ALIVE")), X, Y, Survival.bIsDead ? FColor::Red : FColor::Green);
|
||||||
DrawLine(FString::Printf(TEXT("Shelter: %.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y);
|
DrawLine(FString::Printf(TEXT("Shelter: %.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y);
|
||||||
DrawLine(FString::Printf(TEXT("Expose: x%.2f %+3.1f C"), SurvivalComponent->CurrentWeatherExposureMultiplier, SurvivalComponent->CurrentWeatherTemperatureOffsetC), X, Y);
|
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("Injury: %.0f"), Survival.InjurySeverity), X, Y);
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ void AAgrarianGamePlayerController::AgrarianSurvival()
|
|||||||
|
|
||||||
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
|
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
|
||||||
ClientMessage(FString::Printf(
|
ClientMessage(FString::Printf(
|
||||||
TEXT("Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sprain %.1f | Sickness %.1f"),
|
TEXT("%s | Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sprain %.1f | Sickness %.1f | Death %s"),
|
||||||
|
Survival.bIsDead ? TEXT("DEAD") : TEXT("ALIVE"),
|
||||||
Survival.Health,
|
Survival.Health,
|
||||||
Survival.Stamina,
|
Survival.Stamina,
|
||||||
Survival.Exhaustion,
|
Survival.Exhaustion,
|
||||||
@@ -159,7 +160,8 @@ void AAgrarianGamePlayerController::AgrarianSurvival()
|
|||||||
Survival.InjurySeverity,
|
Survival.InjurySeverity,
|
||||||
Survival.BleedingSeverity,
|
Survival.BleedingSeverity,
|
||||||
Survival.SprainSeverity,
|
Survival.SprainSeverity,
|
||||||
Survival.SicknessSeverity));
|
Survival.SicknessSeverity,
|
||||||
|
*Survival.LastDeathReason.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AAgrarianGamePlayerController::AgrarianHeal()
|
void AAgrarianGamePlayerController::AgrarianHeal()
|
||||||
@@ -322,7 +324,7 @@ void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SurvivalComponent->RestoreHealth(100.0f);
|
SurvivalComponent->Revive(100.0f);
|
||||||
SurvivalComponent->AddFood(100.0f);
|
SurvivalComponent->AddFood(100.0f);
|
||||||
SurvivalComponent->AddWater(100.0f);
|
SurvivalComponent->AddWater(100.0f);
|
||||||
SurvivalComponent->ReduceExhaustion(100.0f);
|
SurvivalComponent->ReduceExhaustion(100.0f);
|
||||||
|
|||||||
@@ -128,7 +128,12 @@ void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProp
|
|||||||
|
|
||||||
bool UAgrarianSurvivalComponent::IsAlive() const
|
bool UAgrarianSurvivalComponent::IsAlive() const
|
||||||
{
|
{
|
||||||
return Survival.Health > 0.0f;
|
return !Survival.bIsDead && Survival.Health > 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UAgrarianSurvivalComponent::IsDead() const
|
||||||
|
{
|
||||||
|
return Survival.bIsDead || Survival.Health <= 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UAgrarianSurvivalComponent::ApplyDamage(float Amount)
|
void UAgrarianSurvivalComponent::ApplyDamage(float Amount)
|
||||||
@@ -145,12 +150,42 @@ void UAgrarianSurvivalComponent::RestoreHealth(float Amount)
|
|||||||
{
|
{
|
||||||
if (GetOwner() && GetOwner()->HasAuthority())
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
{
|
{
|
||||||
|
if (Survival.bIsDead)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Survival.Health += FMath::Max(0.0f, Amount);
|
Survival.Health += FMath::Max(0.0f, Amount);
|
||||||
ClampSurvival();
|
ClampSurvival();
|
||||||
BroadcastSurvivalChanged();
|
BroadcastSurvivalChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UAgrarianSurvivalComponent::MarkDead(FName Reason)
|
||||||
|
{
|
||||||
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
Survival.Health = 0.0f;
|
||||||
|
Survival.Stamina = 0.0f;
|
||||||
|
Survival.bIsDead = true;
|
||||||
|
Survival.LastDeathReason = Reason.IsNone() ? FName(TEXT("unknown")) : Reason;
|
||||||
|
BroadcastSurvivalChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAgrarianSurvivalComponent::Revive(float HealthAmount)
|
||||||
|
{
|
||||||
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
Survival.bIsDead = false;
|
||||||
|
Survival.LastDeathReason = NAME_None;
|
||||||
|
Survival.Health = FMath::Clamp(HealthAmount, 1.0f, 100.0f);
|
||||||
|
Survival.Stamina = FMath::Max(Survival.Stamina, 25.0f);
|
||||||
|
ClampSurvival();
|
||||||
|
BroadcastSurvivalChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UAgrarianSurvivalComponent::AddFood(float Amount)
|
void UAgrarianSurvivalComponent::AddFood(float Amount)
|
||||||
{
|
{
|
||||||
if (GetOwner() && GetOwner()->HasAuthority())
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
@@ -429,6 +464,7 @@ void UAgrarianSurvivalComponent::ClampSurvival()
|
|||||||
Survival.BleedingSeverity = FMath::Clamp(Survival.BleedingSeverity, 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.SprainSeverity = FMath::Clamp(Survival.SprainSeverity, 0.0f, 100.0f);
|
||||||
Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);
|
Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);
|
||||||
|
UpdateDeathState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UAgrarianSurvivalComponent::ClampCareHistory()
|
void UAgrarianSurvivalComponent::ClampCareHistory()
|
||||||
@@ -447,3 +483,21 @@ void UAgrarianSurvivalComponent::BroadcastSurvivalChanged()
|
|||||||
{
|
{
|
||||||
OnSurvivalChanged.Broadcast(Survival);
|
OnSurvivalChanged.Broadcast(Survival);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UAgrarianSurvivalComponent::UpdateDeathState()
|
||||||
|
{
|
||||||
|
if (Survival.Health <= 0.0f)
|
||||||
|
{
|
||||||
|
Survival.Health = 0.0f;
|
||||||
|
Survival.Stamina = 0.0f;
|
||||||
|
Survival.bIsDead = true;
|
||||||
|
if (Survival.LastDeathReason.IsNone())
|
||||||
|
{
|
||||||
|
Survival.LastDeathReason = FName(TEXT("health_depleted"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!Survival.bIsDead)
|
||||||
|
{
|
||||||
|
Survival.LastDeathReason = NAME_None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,12 +84,21 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
bool IsAlive() const;
|
bool IsAlive() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
|
bool IsDead() const;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
void ApplyDamage(float Amount);
|
void ApplyDamage(float Amount);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
void RestoreHealth(float Amount);
|
void RestoreHealth(float Amount);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
|
void MarkDead(FName Reason);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
|
void Revive(float HealthAmount = 100.0f);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||||
void AddFood(float Amount);
|
void AddFood(float Amount);
|
||||||
|
|
||||||
@@ -153,5 +162,6 @@ protected:
|
|||||||
|
|
||||||
void ClampSurvival();
|
void ClampSurvival();
|
||||||
void ClampCareHistory();
|
void ClampCareHistory();
|
||||||
|
void UpdateDeathState();
|
||||||
void BroadcastSurvivalChanged();
|
void BroadcastSurvivalChanged();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -362,6 +362,12 @@ struct FAgrarianSurvivalSnapshot
|
|||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||||
float SicknessSeverity = 0.0f;
|
float SicknessSeverity = 0.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||||
|
bool bIsDead = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||||
|
FName LastDeathReason = NAME_None;
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
|
|||||||
Reference in New Issue
Block a user