Add shelter damage placeholder

This commit is contained in:
2026-05-18 11:19:10 -07:00
parent 01d439c415
commit e75ee716e0
5 changed files with 203 additions and 2 deletions
+4 -1
View File
@@ -631,7 +631,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add shelter protection volume. - [x] Add shelter protection volume.
- [x] Add shelter persistence. - [x] Add shelter persistence.
- [x] Add shelter replication. - [x] Add shelter replication.
- [ ] Add deconstruction or damage placeholder. - [x] Add deconstruction or damage placeholder. Primitive shelters now have
replicated structure health, authority-only damage/repair/deconstruct hooks,
TakeDamage integration, depletion destroy behavior, and save/load support for
current/max structure health.
## 0.1.J Injury And Basic Survival Consequences ## 0.1.J Injury And Basic Survival Consequences
+5
View File
@@ -95,6 +95,11 @@ Early runtime systems should remain small and explicit:
- MVP primitive shelters use an open entrance and do not include an - MVP primitive shelters use an open entrance and do not include an
interactive door. Door actors, locks, ownership permissions, and modular interactive door. Door actors, locks, ownership permissions, and modular
openings are deferred to permanent structures. openings are deferred to permanent structures.
- `AAgrarianShelterActor` includes a version 0.1 structure damage placeholder:
replicated current/max health, authority-only damage, repair, and
deconstruction hooks, `TakeDamage` integration, depletion destruction, and
persistent health state. This gives fire, weather, tools, and future
ownership systems a safe structure-health contract to extend.
- `UAgrarianBuildingPlacementComponent` owns the MVP placement preview. It - `UAgrarianBuildingPlacementComponent` owns the MVP placement preview. It
traces from the player view, snaps to the configured grid, validates distance traces from the player view, snaps to the configured grid, validates distance
and collision, broadcasts Blueprint-readable preview state, and draws a and collision, broadcasts Blueprint-readable preview state, and draws a
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianShelterActor.h"
SOURCE = ROOT / "Source" / "AgrarianGame" / "AgrarianShelterActor.cpp"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
TECHNICAL_DESIGN = ROOT / "Docs" / "TechnicalDesignDocument.md"
def compact(path: Path) -> str:
return " ".join(path.read_text(encoding="utf-8").split())
def require(path: Path, text: str) -> None:
data = compact(path)
if text not in data:
raise SystemExit(f"FAIL: {path.relative_to(ROOT)} missing required text: {text}")
def main() -> None:
require(HEADER, "public AActor, public IAgrarianPersistentStateProvider")
require(HEADER, "MaxStructureHealth")
require(HEADER, "CurrentStructureHealth")
require(HEADER, "ApplyStructureDamage")
require(HEADER, "RepairStructure")
require(HEADER, "Deconstruct")
require(HEADER, "GetStructureHealthRatio")
require(HEADER, "IsStructureDamaged")
require(SOURCE, "DOREPLIFETIME(AAgrarianShelterActor, MaxStructureHealth)")
require(SOURCE, "DOREPLIFETIME(AAgrarianShelterActor, CurrentStructureHealth)")
require(SOURCE, "TakeDamage")
require(SOURCE, "current_structure_health")
require(SOURCE, "max_structure_health")
require(SOURCE, "Destroy()")
require(ROADMAP, "[x] Add deconstruction or damage placeholder.")
require(TECHNICAL_DESIGN, "version 0.1 structure damage placeholder")
print("PASS: shelter damage and deconstruction placeholder is present.")
if __name__ == "__main__":
main()
@@ -4,6 +4,7 @@
#include "AgrarianPersistentActorComponent.h" #include "AgrarianPersistentActorComponent.h"
#include "Components/BoxComponent.h" #include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h" #include "Components/StaticMeshComponent.h"
#include "Net/UnrealNetwork.h"
AAgrarianShelterActor::AAgrarianShelterActor() AAgrarianShelterActor::AAgrarianShelterActor()
{ {
@@ -20,3 +21,113 @@ AAgrarianShelterActor::AAgrarianShelterActor()
PersistentActorComponent = CreateDefaultSubobject<UAgrarianPersistentActorComponent>(TEXT("PersistentActorComponent")); PersistentActorComponent = CreateDefaultSubobject<UAgrarianPersistentActorComponent>(TEXT("PersistentActorComponent"));
PersistentActorComponent->ActorTypeId = TEXT("primitive_shelter"); PersistentActorComponent->ActorTypeId = TEXT("primitive_shelter");
} }
void AAgrarianShelterActor::BeginPlay()
{
Super::BeginPlay();
ClampStructureHealth();
}
float AAgrarianShelterActor::TakeDamage(float DamageAmount, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
const float PreviousHealth = CurrentStructureHealth;
ApplyStructureDamage(DamageAmount, DamageCauser);
return FMath::Max(0.0f, PreviousHealth - CurrentStructureHealth);
}
void AAgrarianShelterActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAgrarianShelterActor, MaxStructureHealth);
DOREPLIFETIME(AAgrarianShelterActor, CurrentStructureHealth);
}
void AAgrarianShelterActor::CapturePersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent) const
{
if (!PersistentComponent)
{
return;
}
PersistentComponent->NumberState.Add(TEXT("max_structure_health"), MaxStructureHealth);
PersistentComponent->NumberState.Add(TEXT("current_structure_health"), CurrentStructureHealth);
}
void AAgrarianShelterActor::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
{
if (!HasAuthority() || !PersistentComponent)
{
return;
}
if (const float* SavedMaxHealth = PersistentComponent->NumberState.Find(TEXT("max_structure_health")))
{
MaxStructureHealth = FMath::Max(1.0f, *SavedMaxHealth);
}
if (const float* SavedCurrentHealth = PersistentComponent->NumberState.Find(TEXT("current_structure_health")))
{
CurrentStructureHealth = *SavedCurrentHealth;
}
ClampStructureHealth();
}
bool AAgrarianShelterActor::ApplyStructureDamage(float DamageAmount, AActor* DamageCauser)
{
if (!HasAuthority() || DamageAmount <= 0.0f || CurrentStructureHealth <= 0.0f)
{
return false;
}
CurrentStructureHealth = FMath::Clamp(CurrentStructureHealth - DamageAmount, 0.0f, MaxStructureHealth);
if (CurrentStructureHealth <= 0.0f && bDestroyWhenHealthDepleted)
{
Destroy();
}
return true;
}
bool AAgrarianShelterActor::RepairStructure(float RepairAmount)
{
if (!HasAuthority() || RepairAmount <= 0.0f || CurrentStructureHealth >= MaxStructureHealth)
{
return false;
}
CurrentStructureHealth = FMath::Clamp(CurrentStructureHealth + RepairAmount, 0.0f, MaxStructureHealth);
return true;
}
bool AAgrarianShelterActor::Deconstruct(AActor* RequestingActor)
{
if (!HasAuthority() || !bCanBeDeconstructed)
{
return false;
}
Destroy();
return true;
}
float AAgrarianShelterActor::GetStructureHealthRatio() const
{
return MaxStructureHealth > 0.0f ? FMath::Clamp(CurrentStructureHealth / MaxStructureHealth, 0.0f, 1.0f) : 0.0f;
}
bool AAgrarianShelterActor::IsStructureDamaged() const
{
return CurrentStructureHealth < MaxStructureHealth;
}
void AAgrarianShelterActor::OnRep_StructureHealth()
{
ClampStructureHealth();
}
void AAgrarianShelterActor::ClampStructureHealth()
{
MaxStructureHealth = FMath::Max(1.0f, MaxStructureHealth);
CurrentStructureHealth = FMath::Clamp(CurrentStructureHealth, 0.0f, MaxStructureHealth);
}
+41 -1
View File
@@ -4,6 +4,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/Actor.h" #include "GameFramework/Actor.h"
#include "AgrarianPersistentStateProvider.h"
#include "AgrarianShelterActor.generated.h" #include "AgrarianShelterActor.generated.h"
class UBoxComponent; class UBoxComponent;
@@ -11,13 +12,19 @@ class UAgrarianPersistentActorComponent;
class UStaticMeshComponent; class UStaticMeshComponent;
UCLASS(Blueprintable) UCLASS(Blueprintable)
class AAgrarianShelterActor : public AActor class AAgrarianShelterActor : public AActor, public IAgrarianPersistentStateProvider
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
AAgrarianShelterActor(); AAgrarianShelterActor();
virtual void BeginPlay() override;
virtual float TakeDamage(float DamageAmount, const FDamageEvent& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void CapturePersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent) const override;
virtual void ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent) override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Shelter") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Shelter")
TObjectPtr<UStaticMeshComponent> Mesh; TObjectPtr<UStaticMeshComponent> Mesh;
@@ -29,4 +36,37 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Shelter", meta = (ClampMin = "0", ClampMax = "1")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Shelter", meta = (ClampMin = "0", ClampMax = "1"))
float WeatherProtection = 0.65f; float WeatherProtection = 0.65f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Shelter|Damage", meta = (ClampMin = "1"))
float MaxStructureHealth = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_StructureHealth, Category = "Agrarian|Shelter|Damage", meta = (ClampMin = "0"))
float CurrentStructureHealth = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Shelter|Damage")
bool bCanBeDeconstructed = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Shelter|Damage")
bool bDestroyWhenHealthDepleted = true;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Shelter|Damage")
bool ApplyStructureDamage(float DamageAmount, AActor* DamageCauser);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Shelter|Damage")
bool RepairStructure(float RepairAmount);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Shelter|Damage")
bool Deconstruct(AActor* RequestingActor);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Agrarian|Shelter|Damage")
float GetStructureHealthRatio() const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Agrarian|Shelter|Damage")
bool IsStructureDamaged() const;
protected:
UFUNCTION()
void OnRep_StructureHealth();
void ClampStructureHealth();
}; };