Add structure ignition risk checks
This commit is contained in:
@@ -839,7 +839,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
|||||||
- [x] Add fire sounds. Added campfire loop, ignition, and extinguish audio hooks with spatialized components, replicated lit-state loop control, and server-triggered multicast event cues so fire audio follows the authoritative campfire state once assets are assigned.
|
- [x] Add fire sounds. Added campfire loop, ignition, and extinguish audio hooks with spatialized components, replicated lit-state loop control, and server-triggered multicast event cues so fire audio follows the authoritative campfire state once assets are assigned.
|
||||||
- [x] Add unattended and poorly maintained fire risk for campfires and other open-flame sources. Added server-side campfire risk state for lit duration, seconds since maintenance, cleared area, containment, high fuel, wet weather mitigation, and a replicated `FireRiskScore` that later ignition/spread systems can consume.
|
- [x] Add unattended and poorly maintained fire risk for campfires and other open-flame sources. Added server-side campfire risk state for lit duration, seconds since maintenance, cleared area, containment, high fuel, wet weather mitigation, and a replicated `FireRiskScore` that later ignition/spread systems can consume.
|
||||||
- [x] Add grass and forest ignition checks from irresponsible fire placement, wind/weather, dry fuel, nearby vegetation, and burn duration. Added foliage fuel counting, campfire vegetation ignition risk scores, grass/brush and forest ignition flags, and weather/wind/burn-duration modifiers so unsafe fire placement near dry fuel can now become a server-authoritative ignition risk.
|
- [x] Add grass and forest ignition checks from irresponsible fire placement, wind/weather, dry fuel, nearby vegetation, and burn duration. Added foliage fuel counting, campfire vegetation ignition risk scores, grass/brush and forest ignition flags, and weather/wind/burn-duration modifiers so unsafe fire placement near dry fuel can now become a server-authoritative ignition risk.
|
||||||
- [ ] Add shelter/structure ignition risk when fires are placed too close to primitive shelters, wood piles, flammable crafting stations, or settlement objects.
|
- [x] Add shelter/structure ignition risk when fires are placed too close to primitive shelters, wood piles, flammable crafting stations, or settlement objects. Added campfire structure ignition risk for nearby primitive shelters and flammable wood/fiber resource nodes, with containment, burn-duration, weather/wind, and fire-risk modifiers before a replicated structure ignition flag is set.
|
||||||
- [ ] Add server-authoritative fire spread rules for grass, brush, trees, shelters, and other burnable actors, including fuel, distance, wind, weather, and suppression hooks.
|
- [ ] Add server-authoritative fire spread rules for grass, brush, trees, shelters, and other burnable actors, including fuel, distance, wind, weather, and suppression hooks.
|
||||||
- [ ] Add fire maintenance gameplay so watched, cleared, contained, or extinguished fires are safe, while neglected fires can become dangerous.
|
- [ ] Add fire maintenance gameplay so watched, cleared, contained, or extinguished fires are safe, while neglected fires can become dangerous.
|
||||||
- [ ] Add fire suppression hooks for rain, water carrying, dirt/sand, cleared firebreaks, and future firefighting tools.
|
- [ ] Add fire suppression hooks for rain, water carrying, dirt/sand, cleared firebreaks, and future firefighting tools.
|
||||||
|
|||||||
@@ -353,6 +353,14 @@ duration, wind speed, current weather, cleared-area maintenance, and the
|
|||||||
campfire's current risk ratio. Reaching the threshold marks the relevant
|
campfire's current risk ratio. Reaching the threshold marks the relevant
|
||||||
ignition flag, while later spread rules decide how active fires propagate.
|
ignition flag, while later spread rules decide how active fires propagate.
|
||||||
|
|
||||||
|
Structure ignition risk uses the same server-authoritative campfire risk model.
|
||||||
|
Open fires check nearby primitive shelters plus flammable wood/fiber resource
|
||||||
|
nodes as MVP stand-ins for wood piles, flammable crafting stations, and future
|
||||||
|
settlement objects. Contained fires skip this structure check; otherwise
|
||||||
|
structure risk accumulates with distance-based fuel presence, burn duration,
|
||||||
|
weather/wind, and the current fire-risk ratio before setting a replicated
|
||||||
|
structure ignition flag.
|
||||||
|
|
||||||
Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
|
Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
|
||||||
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
|
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
|
||||||
the same visual update path as natural fuel depletion.
|
the same visual update path as natural fuel depletion.
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify shelter/structure ignition risk is tracked from nearby flammable actors."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
FIRE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.h"
|
||||||
|
FIRE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.cpp"
|
||||||
|
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
|
||||||
|
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||||
|
|
||||||
|
REQUIRED = {
|
||||||
|
FIRE_H: [
|
||||||
|
"StructureIgnitionRiskScore",
|
||||||
|
"bStructureIgnited",
|
||||||
|
"StructureIgnitionCheckRadius",
|
||||||
|
"StructureIgnitionFuelScoreThreshold",
|
||||||
|
"StructureIgnitionRiskPerSecond",
|
||||||
|
"UpdateStructureIgnitionRisk",
|
||||||
|
"GetStructureFuelScoreNearFire",
|
||||||
|
],
|
||||||
|
FIRE_CPP: [
|
||||||
|
"#include \"AgrarianResourceNode.h\"",
|
||||||
|
"#include \"AgrarianShelterActor.h\"",
|
||||||
|
"DOREPLIFETIME(AAgrarianCampfire, StructureIgnitionRiskScore)",
|
||||||
|
"DOREPLIFETIME(AAgrarianCampfire, bStructureIgnited)",
|
||||||
|
"UpdateStructureIgnitionRisk(DeltaSeconds);",
|
||||||
|
"AAgrarianCampfire::UpdateStructureIgnitionRisk",
|
||||||
|
"AAgrarianCampfire::GetStructureFuelScoreNearFire",
|
||||||
|
"AAgrarianShelterActor::StaticClass()",
|
||||||
|
"AAgrarianResourceNode::StaticClass()",
|
||||||
|
"YieldId == TEXT(\"wood\") || YieldId == TEXT(\"fiber\")",
|
||||||
|
"bFireContained",
|
||||||
|
"bStructureIgnited = StructureIgnitionRiskScore >= 100.0f",
|
||||||
|
"structure_ignition_risk_score",
|
||||||
|
"structure_ignited",
|
||||||
|
],
|
||||||
|
TDD: [
|
||||||
|
"Structure ignition risk uses the same server-authoritative campfire risk model",
|
||||||
|
"primitive shelters",
|
||||||
|
"flammable wood/fiber resource",
|
||||||
|
"structure ignition flag",
|
||||||
|
],
|
||||||
|
ROADMAP: [
|
||||||
|
"[x] Add shelter/structure ignition risk",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
missing = []
|
||||||
|
for path, snippets in REQUIRED.items():
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
for snippet in snippets:
|
||||||
|
if snippet not in text:
|
||||||
|
missing.append(f"{path.relative_to(ROOT)} missing {snippet!r}")
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("FAILED: " + "; ".join(missing))
|
||||||
|
print("OK: structure ignition risk checks are wired to nearby flammable actors.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "AgrarianGameState.h"
|
#include "AgrarianGameState.h"
|
||||||
#include "AgrarianInventoryComponent.h"
|
#include "AgrarianInventoryComponent.h"
|
||||||
#include "AgrarianPersistentActorComponent.h"
|
#include "AgrarianPersistentActorComponent.h"
|
||||||
|
#include "AgrarianResourceNode.h"
|
||||||
|
#include "AgrarianShelterActor.h"
|
||||||
#include "AgrarianSurvivalComponent.h"
|
#include "AgrarianSurvivalComponent.h"
|
||||||
#include "Particles/ParticleSystemComponent.h"
|
#include "Particles/ParticleSystemComponent.h"
|
||||||
#include "Components/AudioComponent.h"
|
#include "Components/AudioComponent.h"
|
||||||
@@ -143,6 +145,7 @@ void AAgrarianCampfire::Tick(float DeltaSeconds)
|
|||||||
|
|
||||||
UpdateFireRisk(DeltaSeconds);
|
UpdateFireRisk(DeltaSeconds);
|
||||||
UpdateVegetationIgnitionRisk(DeltaSeconds);
|
UpdateVegetationIgnitionRisk(DeltaSeconds);
|
||||||
|
UpdateStructureIgnitionRisk(DeltaSeconds);
|
||||||
WarmNearbyCharacters(DeltaSeconds);
|
WarmNearbyCharacters(DeltaSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,6 +167,8 @@ void AAgrarianCampfire::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Ou
|
|||||||
DOREPLIFETIME(AAgrarianCampfire, ForestIgnitionRiskScore);
|
DOREPLIFETIME(AAgrarianCampfire, ForestIgnitionRiskScore);
|
||||||
DOREPLIFETIME(AAgrarianCampfire, bGrassOrBrushIgnited);
|
DOREPLIFETIME(AAgrarianCampfire, bGrassOrBrushIgnited);
|
||||||
DOREPLIFETIME(AAgrarianCampfire, bForestFuelIgnited);
|
DOREPLIFETIME(AAgrarianCampfire, bForestFuelIgnited);
|
||||||
|
DOREPLIFETIME(AAgrarianCampfire, StructureIgnitionRiskScore);
|
||||||
|
DOREPLIFETIME(AAgrarianCampfire, bStructureIgnited);
|
||||||
}
|
}
|
||||||
|
|
||||||
FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
|
FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
|
||||||
@@ -211,6 +216,8 @@ void AAgrarianCampfire::CapturePersistentState_Implementation(UAgrarianPersisten
|
|||||||
PersistentComponent->NumberState.Add(TEXT("forest_ignition_risk_score"), ForestIgnitionRiskScore);
|
PersistentComponent->NumberState.Add(TEXT("forest_ignition_risk_score"), ForestIgnitionRiskScore);
|
||||||
PersistentComponent->NumberState.Add(TEXT("grass_or_brush_ignited"), bGrassOrBrushIgnited ? 1.0f : 0.0f);
|
PersistentComponent->NumberState.Add(TEXT("grass_or_brush_ignited"), bGrassOrBrushIgnited ? 1.0f : 0.0f);
|
||||||
PersistentComponent->NumberState.Add(TEXT("forest_fuel_ignited"), bForestFuelIgnited ? 1.0f : 0.0f);
|
PersistentComponent->NumberState.Add(TEXT("forest_fuel_ignited"), bForestFuelIgnited ? 1.0f : 0.0f);
|
||||||
|
PersistentComponent->NumberState.Add(TEXT("structure_ignition_risk_score"), StructureIgnitionRiskScore);
|
||||||
|
PersistentComponent->NumberState.Add(TEXT("structure_ignited"), bStructureIgnited ? 1.0f : 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
|
void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
|
||||||
@@ -234,6 +241,8 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
|
|||||||
const float* SavedForestIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("forest_ignition_risk_score"));
|
const float* SavedForestIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("forest_ignition_risk_score"));
|
||||||
const float* SavedGrassIgnited = PersistentComponent->NumberState.Find(TEXT("grass_or_brush_ignited"));
|
const float* SavedGrassIgnited = PersistentComponent->NumberState.Find(TEXT("grass_or_brush_ignited"));
|
||||||
const float* SavedForestIgnited = PersistentComponent->NumberState.Find(TEXT("forest_fuel_ignited"));
|
const float* SavedForestIgnited = PersistentComponent->NumberState.Find(TEXT("forest_fuel_ignited"));
|
||||||
|
const float* SavedStructureIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("structure_ignition_risk_score"));
|
||||||
|
const float* SavedStructureIgnited = PersistentComponent->NumberState.Find(TEXT("structure_ignited"));
|
||||||
|
|
||||||
if (SavedFuelSeconds)
|
if (SavedFuelSeconds)
|
||||||
{
|
{
|
||||||
@@ -300,6 +309,16 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
|
|||||||
bForestFuelIgnited = *SavedForestIgnited > 0.5f;
|
bForestFuelIgnited = *SavedForestIgnited > 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SavedStructureIgnitionRisk)
|
||||||
|
{
|
||||||
|
StructureIgnitionRiskScore = FMath::Clamp(*SavedStructureIgnitionRisk, 0.0f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SavedStructureIgnited)
|
||||||
|
{
|
||||||
|
bStructureIgnited = *SavedStructureIgnited > 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f);
|
SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +346,7 @@ void AAgrarianCampfire::Extinguish()
|
|||||||
FireRiskScore = 0.0f;
|
FireRiskScore = 0.0f;
|
||||||
GrassIgnitionRiskScore = 0.0f;
|
GrassIgnitionRiskScore = 0.0f;
|
||||||
ForestIgnitionRiskScore = 0.0f;
|
ForestIgnitionRiskScore = 0.0f;
|
||||||
|
StructureIgnitionRiskScore = 0.0f;
|
||||||
LitDurationSeconds = 0.0f;
|
LitDurationSeconds = 0.0f;
|
||||||
SecondsSinceMaintenance = 0.0f;
|
SecondsSinceMaintenance = 0.0f;
|
||||||
SetLit(false);
|
SetLit(false);
|
||||||
@@ -649,3 +669,62 @@ float AAgrarianCampfire::GetVegetationIgnitionWeatherMultiplier() const
|
|||||||
|
|
||||||
return FMath::Max(0.0f, Multiplier);
|
return FMath::Max(0.0f, Multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AAgrarianCampfire::UpdateStructureIgnitionRisk(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
if (!HasAuthority() || !bLit || bFireContained || bStructureIgnited)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float StructureFuelScore = GetStructureFuelScoreNearFire();
|
||||||
|
if (StructureFuelScore < StructureIgnitionFuelScoreThreshold)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float BurnDurationMultiplier = FMath::GetMappedRangeValueClamped(
|
||||||
|
FVector2D(15.0f, 240.0f),
|
||||||
|
FVector2D(0.45f, 1.5f),
|
||||||
|
LitDurationSeconds);
|
||||||
|
const float RiskMultiplier = BurnDurationMultiplier * GetVegetationIgnitionWeatherMultiplier() * FMath::Max(0.0f, GetFireRiskRatio());
|
||||||
|
StructureIgnitionRiskScore = FMath::Clamp(
|
||||||
|
StructureIgnitionRiskScore + (StructureIgnitionRiskPerSecond * StructureFuelScore * RiskMultiplier * DeltaSeconds),
|
||||||
|
0.0f,
|
||||||
|
100.0f);
|
||||||
|
bStructureIgnited = StructureIgnitionRiskScore >= 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float AAgrarianCampfire::GetStructureFuelScoreNearFire() const
|
||||||
|
{
|
||||||
|
float StructureFuelScore = 0.0f;
|
||||||
|
|
||||||
|
TArray<AActor*> ShelterActors;
|
||||||
|
UGameplayStatics::GetAllActorsOfClass(this, AAgrarianShelterActor::StaticClass(), ShelterActors);
|
||||||
|
for (const AActor* Actor : ShelterActors)
|
||||||
|
{
|
||||||
|
if (Actor && Actor != this && FVector::DistSquared2D(Actor->GetActorLocation(), GetActorLocation()) <= FMath::Square(StructureIgnitionCheckRadius))
|
||||||
|
{
|
||||||
|
StructureFuelScore += 4.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<AActor*> ResourceActors;
|
||||||
|
UGameplayStatics::GetAllActorsOfClass(this, AAgrarianResourceNode::StaticClass(), ResourceActors);
|
||||||
|
for (const AActor* Actor : ResourceActors)
|
||||||
|
{
|
||||||
|
const AAgrarianResourceNode* ResourceNode = Cast<AAgrarianResourceNode>(Actor);
|
||||||
|
if (!ResourceNode || FVector::DistSquared2D(ResourceNode->GetActorLocation(), GetActorLocation()) > FMath::Square(StructureIgnitionCheckRadius))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FName YieldId = ResourceNode->YieldItem.ItemId;
|
||||||
|
if (YieldId == TEXT("wood") || YieldId == TEXT("fiber"))
|
||||||
|
{
|
||||||
|
StructureFuelScore += 1.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StructureFuelScore;
|
||||||
|
}
|
||||||
|
|||||||
@@ -135,6 +135,21 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0"))
|
||||||
float VegetationIgnitionRiskPerSecond = 0.035f;
|
float VegetationIgnitionRiskPerSecond = 0.035f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Structure", meta = (ClampMin = "0", ClampMax = "100"))
|
||||||
|
float StructureIgnitionRiskScore = 0.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Structure")
|
||||||
|
bool bStructureIgnited = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Structure", meta = (ClampMin = "0"))
|
||||||
|
float StructureIgnitionCheckRadius = 650.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Structure", meta = (ClampMin = "0"))
|
||||||
|
float StructureIgnitionFuelScoreThreshold = 2.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Structure", meta = (ClampMin = "0"))
|
||||||
|
float StructureIgnitionRiskPerSecond = 0.08f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Weather", meta = (ClampMin = "1"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Weather", meta = (ClampMin = "1"))
|
||||||
float RainFuelDrainMultiplier = 1.5f;
|
float RainFuelDrainMultiplier = 1.5f;
|
||||||
|
|
||||||
@@ -202,4 +217,6 @@ protected:
|
|||||||
void UpdateVegetationIgnitionRisk(float DeltaSeconds);
|
void UpdateVegetationIgnitionRisk(float DeltaSeconds);
|
||||||
float GetVegetationFuelScoreNearFire(float& OutGrassFuelScore, float& OutForestFuelScore) const;
|
float GetVegetationFuelScoreNearFire(float& OutGrassFuelScore, float& OutForestFuelScore) const;
|
||||||
float GetVegetationIgnitionWeatherMultiplier() const;
|
float GetVegetationIgnitionWeatherMultiplier() const;
|
||||||
|
void UpdateStructureIgnitionRisk(float DeltaSeconds);
|
||||||
|
float GetStructureFuelScoreNearFire() const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user