Add vegetation ignition risk checks
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Copyright Pacificao. All Rights Reserved.
|
||||
|
||||
#include "AgrarianCampfire.h"
|
||||
#include "AgrarianFoliagePatch.h"
|
||||
#include "AgrarianGameCharacter.h"
|
||||
#include "AgrarianGameState.h"
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
@@ -141,6 +142,7 @@ void AAgrarianCampfire::Tick(float DeltaSeconds)
|
||||
}
|
||||
|
||||
UpdateFireRisk(DeltaSeconds);
|
||||
UpdateVegetationIgnitionRisk(DeltaSeconds);
|
||||
WarmNearbyCharacters(DeltaSeconds);
|
||||
}
|
||||
}
|
||||
@@ -158,6 +160,10 @@ void AAgrarianCampfire::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Ou
|
||||
DOREPLIFETIME(AAgrarianCampfire, SecondsSinceMaintenance);
|
||||
DOREPLIFETIME(AAgrarianCampfire, bFireAreaCleared);
|
||||
DOREPLIFETIME(AAgrarianCampfire, bFireContained);
|
||||
DOREPLIFETIME(AAgrarianCampfire, GrassIgnitionRiskScore);
|
||||
DOREPLIFETIME(AAgrarianCampfire, ForestIgnitionRiskScore);
|
||||
DOREPLIFETIME(AAgrarianCampfire, bGrassOrBrushIgnited);
|
||||
DOREPLIFETIME(AAgrarianCampfire, bForestFuelIgnited);
|
||||
}
|
||||
|
||||
FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
|
||||
@@ -201,6 +207,10 @@ void AAgrarianCampfire::CapturePersistentState_Implementation(UAgrarianPersisten
|
||||
PersistentComponent->NumberState.Add(TEXT("seconds_since_maintenance"), SecondsSinceMaintenance);
|
||||
PersistentComponent->NumberState.Add(TEXT("fire_area_cleared"), bFireAreaCleared ? 1.0f : 0.0f);
|
||||
PersistentComponent->NumberState.Add(TEXT("fire_contained"), bFireContained ? 1.0f : 0.0f);
|
||||
PersistentComponent->NumberState.Add(TEXT("grass_ignition_risk_score"), GrassIgnitionRiskScore);
|
||||
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("forest_fuel_ignited"), bForestFuelIgnited ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
|
||||
@@ -220,6 +230,10 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
|
||||
const float* SavedSecondsSinceMaintenance = PersistentComponent->NumberState.Find(TEXT("seconds_since_maintenance"));
|
||||
const float* SavedAreaCleared = PersistentComponent->NumberState.Find(TEXT("fire_area_cleared"));
|
||||
const float* SavedContained = PersistentComponent->NumberState.Find(TEXT("fire_contained"));
|
||||
const float* SavedGrassIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("grass_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* SavedForestIgnited = PersistentComponent->NumberState.Find(TEXT("forest_fuel_ignited"));
|
||||
|
||||
if (SavedFuelSeconds)
|
||||
{
|
||||
@@ -266,6 +280,26 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
|
||||
bFireContained = *SavedContained > 0.5f;
|
||||
}
|
||||
|
||||
if (SavedGrassIgnitionRisk)
|
||||
{
|
||||
GrassIgnitionRiskScore = FMath::Clamp(*SavedGrassIgnitionRisk, 0.0f, 100.0f);
|
||||
}
|
||||
|
||||
if (SavedForestIgnitionRisk)
|
||||
{
|
||||
ForestIgnitionRiskScore = FMath::Clamp(*SavedForestIgnitionRisk, 0.0f, 100.0f);
|
||||
}
|
||||
|
||||
if (SavedGrassIgnited)
|
||||
{
|
||||
bGrassOrBrushIgnited = *SavedGrassIgnited > 0.5f;
|
||||
}
|
||||
|
||||
if (SavedForestIgnited)
|
||||
{
|
||||
bForestFuelIgnited = *SavedForestIgnited > 0.5f;
|
||||
}
|
||||
|
||||
SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f);
|
||||
}
|
||||
|
||||
@@ -291,6 +325,8 @@ void AAgrarianCampfire::Extinguish()
|
||||
{
|
||||
FuelSeconds = 0.0f;
|
||||
FireRiskScore = 0.0f;
|
||||
GrassIgnitionRiskScore = 0.0f;
|
||||
ForestIgnitionRiskScore = 0.0f;
|
||||
LitDurationSeconds = 0.0f;
|
||||
SecondsSinceMaintenance = 0.0f;
|
||||
SetLit(false);
|
||||
@@ -517,3 +553,99 @@ float AAgrarianCampfire::GetFireRiskGrowthPerSecond() const
|
||||
|
||||
return FMath::Max(0.0f, RiskGrowth);
|
||||
}
|
||||
|
||||
void AAgrarianCampfire::UpdateVegetationIgnitionRisk(float DeltaSeconds)
|
||||
{
|
||||
if (!HasAuthority() || !bLit || bFireAreaCleared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float GrassFuelScore = 0.0f;
|
||||
float ForestFuelScore = 0.0f;
|
||||
const float TotalFuelScore = GetVegetationFuelScoreNearFire(GrassFuelScore, ForestFuelScore);
|
||||
if (TotalFuelScore < VegetationIgnitionFuelScoreThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float BurnDurationMultiplier = FMath::GetMappedRangeValueClamped(
|
||||
FVector2D(30.0f, 300.0f),
|
||||
FVector2D(0.35f, 1.4f),
|
||||
LitDurationSeconds);
|
||||
const float RiskMultiplier = GetVegetationIgnitionWeatherMultiplier() * BurnDurationMultiplier * FMath::Max(0.0f, GetFireRiskRatio());
|
||||
const float BaseRisk = VegetationIgnitionRiskPerSecond * DeltaSeconds * RiskMultiplier;
|
||||
|
||||
if (GrassFuelScore > 0.0f && !bGrassOrBrushIgnited)
|
||||
{
|
||||
GrassIgnitionRiskScore = FMath::Clamp(GrassIgnitionRiskScore + (BaseRisk * GrassFuelScore), 0.0f, 100.0f);
|
||||
bGrassOrBrushIgnited = GrassIgnitionRiskScore >= 100.0f;
|
||||
}
|
||||
|
||||
if (ForestFuelScore > 0.0f && !bForestFuelIgnited)
|
||||
{
|
||||
ForestIgnitionRiskScore = FMath::Clamp(ForestIgnitionRiskScore + (BaseRisk * ForestFuelScore * 0.6f), 0.0f, 100.0f);
|
||||
bForestFuelIgnited = ForestIgnitionRiskScore >= 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float AAgrarianCampfire::GetVegetationFuelScoreNearFire(float& OutGrassFuelScore, float& OutForestFuelScore) const
|
||||
{
|
||||
OutGrassFuelScore = 0.0f;
|
||||
OutForestFuelScore = 0.0f;
|
||||
|
||||
TArray<AActor*> FoliageActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(this, AAgrarianFoliagePatch::StaticClass(), FoliageActors);
|
||||
for (AActor* Actor : FoliageActors)
|
||||
{
|
||||
const AAgrarianFoliagePatch* FoliagePatch = Cast<AAgrarianFoliagePatch>(Actor);
|
||||
if (!FoliagePatch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int32 GrassCount = 0;
|
||||
int32 ShrubCount = 0;
|
||||
int32 TreeCount = 0;
|
||||
FoliagePatch->GetFuelCountsNearLocation(GetActorLocation(), VegetationIgnitionCheckRadius, GrassCount, ShrubCount, TreeCount);
|
||||
OutGrassFuelScore += static_cast<float>(GrassCount) + (static_cast<float>(ShrubCount) * 1.5f);
|
||||
OutForestFuelScore += static_cast<float>(TreeCount) * 3.0f;
|
||||
}
|
||||
|
||||
return OutGrassFuelScore + OutForestFuelScore;
|
||||
}
|
||||
|
||||
float AAgrarianCampfire::GetVegetationIgnitionWeatherMultiplier() const
|
||||
{
|
||||
float Multiplier = 1.0f;
|
||||
if (const UWorld* World = GetWorld())
|
||||
{
|
||||
if (const AAgrarianGameState* GameState = World->GetGameState<AAgrarianGameState>())
|
||||
{
|
||||
if (GameState->ActiveWeatherInputs.bHasProviderData)
|
||||
{
|
||||
Multiplier *= FMath::GetMappedRangeValueClamped(
|
||||
FVector2D(0.0f, 55.0f),
|
||||
FVector2D(0.85f, 1.65f),
|
||||
GameState->ActiveWeatherInputs.WindSpeedKmh);
|
||||
}
|
||||
|
||||
switch (GameState->Weather)
|
||||
{
|
||||
case EAgrarianWeatherType::Rain:
|
||||
Multiplier *= 0.2f;
|
||||
break;
|
||||
case EAgrarianWeatherType::Storm:
|
||||
Multiplier *= 0.1f;
|
||||
break;
|
||||
case EAgrarianWeatherType::ColdWind:
|
||||
Multiplier *= 1.25f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FMath::Max(0.0f, Multiplier);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,27 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Risk", meta = (ClampMin = "0", ClampMax = "1"))
|
||||
float ContainedFireRiskMultiplier = 0.35f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0", ClampMax = "100"))
|
||||
float GrassIgnitionRiskScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0", ClampMax = "100"))
|
||||
float ForestIgnitionRiskScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Vegetation")
|
||||
bool bGrassOrBrushIgnited = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Vegetation")
|
||||
bool bForestFuelIgnited = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0"))
|
||||
float VegetationIgnitionCheckRadius = 700.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0"))
|
||||
float VegetationIgnitionFuelScoreThreshold = 8.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Vegetation", meta = (ClampMin = "0"))
|
||||
float VegetationIgnitionRiskPerSecond = 0.035f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Weather", meta = (ClampMin = "1"))
|
||||
float RainFuelDrainMultiplier = 1.5f;
|
||||
|
||||
@@ -178,4 +199,7 @@ protected:
|
||||
void WarmNearbyCharacters(float DeltaSeconds);
|
||||
void UpdateFireRisk(float DeltaSeconds);
|
||||
float GetFireRiskGrowthPerSecond() const;
|
||||
void UpdateVegetationIgnitionRisk(float DeltaSeconds);
|
||||
float GetVegetationFuelScoreNearFire(float& OutGrassFuelScore, float& OutForestFuelScore) const;
|
||||
float GetVegetationIgnitionWeatherMultiplier() const;
|
||||
};
|
||||
|
||||
@@ -106,3 +106,53 @@ int32 AAgrarianFoliagePatch::GetGrassInstanceCount() const
|
||||
{
|
||||
return GrassInstances ? GrassInstances->GetInstanceCount() : 0;
|
||||
}
|
||||
|
||||
void AAgrarianFoliagePatch::GetFuelCountsNearLocation(
|
||||
const FVector& WorldLocation,
|
||||
float Radius,
|
||||
int32& OutGrassCount,
|
||||
int32& OutShrubCount,
|
||||
int32& OutTreeCount) const
|
||||
{
|
||||
SCOPE_CYCLE_COUNTER(STAT_AgrarianFoliageInstanceMutation);
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianFoliageFuelCountsNearLocation);
|
||||
|
||||
OutGrassCount = CountInstancesNearLocation(GrassInstances, WorldLocation, Radius);
|
||||
OutShrubCount = CountInstancesNearLocation(ShrubInstances, WorldLocation, Radius);
|
||||
OutTreeCount = CountInstancesNearLocation(TreeInstances, WorldLocation, Radius);
|
||||
}
|
||||
|
||||
float AAgrarianFoliagePatch::GetDryVegetationFuelScoreNearLocation(const FVector& WorldLocation, float Radius) const
|
||||
{
|
||||
int32 GrassCount = 0;
|
||||
int32 ShrubCount = 0;
|
||||
int32 TreeCount = 0;
|
||||
GetFuelCountsNearLocation(WorldLocation, Radius, GrassCount, ShrubCount, TreeCount);
|
||||
|
||||
return static_cast<float>(GrassCount) + (static_cast<float>(ShrubCount) * 2.0f) + (static_cast<float>(TreeCount) * 4.0f);
|
||||
}
|
||||
|
||||
int32 AAgrarianFoliagePatch::CountInstancesNearLocation(
|
||||
const UHierarchicalInstancedStaticMeshComponent* Component,
|
||||
const FVector& WorldLocation,
|
||||
float Radius) const
|
||||
{
|
||||
if (!Component || Radius <= 0.0f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const float RadiusSquared = FMath::Square(Radius);
|
||||
int32 Count = 0;
|
||||
for (int32 Index = 0; Index < Component->GetInstanceCount(); ++Index)
|
||||
{
|
||||
FTransform InstanceTransform;
|
||||
if (Component->GetInstanceTransform(Index, InstanceTransform, true)
|
||||
&& FVector::DistSquared2D(InstanceTransform.GetLocation(), WorldLocation) <= RadiusSquared)
|
||||
{
|
||||
++Count;
|
||||
}
|
||||
}
|
||||
|
||||
return Count;
|
||||
}
|
||||
|
||||
@@ -49,4 +49,21 @@ public:
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|Foliage")
|
||||
int32 GetGrassInstanceCount() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Foliage|Fire")
|
||||
void GetFuelCountsNearLocation(
|
||||
const FVector& WorldLocation,
|
||||
float Radius,
|
||||
int32& OutGrassCount,
|
||||
int32& OutShrubCount,
|
||||
int32& OutTreeCount) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|Foliage|Fire")
|
||||
float GetDryVegetationFuelScoreNearLocation(const FVector& WorldLocation, float Radius) const;
|
||||
|
||||
protected:
|
||||
int32 CountInstancesNearLocation(
|
||||
const UHierarchicalInstancedStaticMeshComponent* Component,
|
||||
const FVector& WorldLocation,
|
||||
float Radius) const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user