Add shelter damage placeholder
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user