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 persistence.
- [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
+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
interactive door. Door actors, locks, ownership permissions, and modular
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
traces from the player view, snaps to the configured grid, validates distance
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 "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Net/UnrealNetwork.h"
AAgrarianShelterActor::AAgrarianShelterActor()
{
@@ -20,3 +21,113 @@ AAgrarianShelterActor::AAgrarianShelterActor()
PersistentActorComponent = CreateDefaultSubobject<UAgrarianPersistentActorComponent>(TEXT("PersistentActorComponent"));
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 "GameFramework/Actor.h"
#include "AgrarianPersistentStateProvider.h"
#include "AgrarianShelterActor.generated.h"
class UBoxComponent;
@@ -11,13 +12,19 @@ class UAgrarianPersistentActorComponent;
class UStaticMeshComponent;
UCLASS(Blueprintable)
class AAgrarianShelterActor : public AActor
class AAgrarianShelterActor : public AActor, public IAgrarianPersistentStateProvider
{
GENERATED_BODY()
public:
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")
TObjectPtr<UStaticMeshComponent> Mesh;
@@ -29,4 +36,37 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Shelter", meta = (ClampMin = "0", ClampMax = "1"))
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();
};