Add server authoritative fire spread state

This commit is contained in:
2026-05-19 12:19:15 -07:00
parent 168cd0e61f
commit dde42bf452
5 changed files with 201 additions and 1 deletions
+98
View File
@@ -146,6 +146,7 @@ void AAgrarianCampfire::Tick(float DeltaSeconds)
UpdateFireRisk(DeltaSeconds);
UpdateVegetationIgnitionRisk(DeltaSeconds);
UpdateStructureIgnitionRisk(DeltaSeconds);
UpdateServerAuthoritativeFireSpread(DeltaSeconds);
WarmNearbyCharacters(DeltaSeconds);
}
}
@@ -169,6 +170,10 @@ void AAgrarianCampfire::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Ou
DOREPLIFETIME(AAgrarianCampfire, bForestFuelIgnited);
DOREPLIFETIME(AAgrarianCampfire, StructureIgnitionRiskScore);
DOREPLIFETIME(AAgrarianCampfire, bStructureIgnited);
DOREPLIFETIME(AAgrarianCampfire, GrassFireIntensity);
DOREPLIFETIME(AAgrarianCampfire, ForestFireIntensity);
DOREPLIFETIME(AAgrarianCampfire, StructureFireIntensity);
DOREPLIFETIME(AAgrarianCampfire, ActiveFireSpreadRadius);
}
FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
@@ -218,6 +223,10 @@ void AAgrarianCampfire::CapturePersistentState_Implementation(UAgrarianPersisten
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);
PersistentComponent->NumberState.Add(TEXT("grass_fire_intensity"), GrassFireIntensity);
PersistentComponent->NumberState.Add(TEXT("forest_fire_intensity"), ForestFireIntensity);
PersistentComponent->NumberState.Add(TEXT("structure_fire_intensity"), StructureFireIntensity);
PersistentComponent->NumberState.Add(TEXT("active_fire_spread_radius"), ActiveFireSpreadRadius);
}
void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
@@ -243,6 +252,10 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
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"));
const float* SavedGrassFireIntensity = PersistentComponent->NumberState.Find(TEXT("grass_fire_intensity"));
const float* SavedForestFireIntensity = PersistentComponent->NumberState.Find(TEXT("forest_fire_intensity"));
const float* SavedStructureFireIntensity = PersistentComponent->NumberState.Find(TEXT("structure_fire_intensity"));
const float* SavedActiveFireSpreadRadius = PersistentComponent->NumberState.Find(TEXT("active_fire_spread_radius"));
if (SavedFuelSeconds)
{
@@ -319,6 +332,26 @@ void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentA
bStructureIgnited = *SavedStructureIgnited > 0.5f;
}
if (SavedGrassFireIntensity)
{
GrassFireIntensity = FMath::Clamp(*SavedGrassFireIntensity, 0.0f, 100.0f);
}
if (SavedForestFireIntensity)
{
ForestFireIntensity = FMath::Clamp(*SavedForestFireIntensity, 0.0f, 100.0f);
}
if (SavedStructureFireIntensity)
{
StructureFireIntensity = FMath::Clamp(*SavedStructureFireIntensity, 0.0f, 100.0f);
}
if (SavedActiveFireSpreadRadius)
{
ActiveFireSpreadRadius = FMath::Clamp(*SavedActiveFireSpreadRadius, 0.0f, MaxFireSpreadRadius);
}
SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f);
}
@@ -347,6 +380,10 @@ void AAgrarianCampfire::Extinguish()
GrassIgnitionRiskScore = 0.0f;
ForestIgnitionRiskScore = 0.0f;
StructureIgnitionRiskScore = 0.0f;
GrassFireIntensity = 0.0f;
ForestFireIntensity = 0.0f;
StructureFireIntensity = 0.0f;
ActiveFireSpreadRadius = 0.0f;
LitDurationSeconds = 0.0f;
SecondsSinceMaintenance = 0.0f;
SetLit(false);
@@ -728,3 +765,64 @@ float AAgrarianCampfire::GetStructureFuelScoreNearFire() const
return StructureFuelScore;
}
void AAgrarianCampfire::UpdateServerAuthoritativeFireSpread(float DeltaSeconds)
{
if (!HasAuthority())
{
return;
}
const bool bAnyActiveFire = bGrassOrBrushIgnited || bForestFuelIgnited || bStructureIgnited;
if (!bAnyActiveFire)
{
return;
}
const float WeatherMultiplier = GetFireSpreadWeatherMultiplier();
const float SuppressionMultiplier = FMath::Clamp(1.0f - FireSuppressionPressure, 0.0f, 1.0f);
const float FuelScore = FMath::Max(1.0f, GetActiveBurningFuelScore());
const float IntensityDelta = FireSpreadIntensityPerSecond * WeatherMultiplier * SuppressionMultiplier * FuelScore * DeltaSeconds;
if (bGrassOrBrushIgnited)
{
GrassFireIntensity = FMath::Clamp(GrassFireIntensity + IntensityDelta, 0.0f, 100.0f);
}
if (bForestFuelIgnited)
{
ForestFireIntensity = FMath::Clamp(ForestFireIntensity + (IntensityDelta * 0.75f), 0.0f, 100.0f);
}
if (bStructureIgnited)
{
StructureFireIntensity = FMath::Clamp(StructureFireIntensity + (IntensityDelta * 0.9f), 0.0f, 100.0f);
}
const float TotalIntensity = GrassFireIntensity + ForestFireIntensity + StructureFireIntensity;
ActiveFireSpreadRadius = FMath::Clamp(
BaseFireSpreadRadius + (TotalIntensity * 12.0f * WeatherMultiplier),
0.0f,
MaxFireSpreadRadius);
}
float AAgrarianCampfire::GetFireSpreadWeatherMultiplier() const
{
float Multiplier = GetVegetationIgnitionWeatherMultiplier();
if (IsWetWeatherActive())
{
Multiplier *= 0.5f;
}
return FMath::Max(0.0f, Multiplier);
}
float AAgrarianCampfire::GetActiveBurningFuelScore() const
{
float GrassFuelScore = 0.0f;
float ForestFuelScore = 0.0f;
const float VegetationFuelScore = GetVegetationFuelScoreNearFire(GrassFuelScore, ForestFuelScore);
const float StructureFuelScore = GetStructureFuelScoreNearFire();
return FMath::Max(0.0f, VegetationFuelScore + StructureFuelScore);
}
+27
View File
@@ -150,6 +150,30 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Structure", meta = (ClampMin = "0"))
float StructureIgnitionRiskPerSecond = 0.08f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0", ClampMax = "100"))
float GrassFireIntensity = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0", ClampMax = "100"))
float ForestFireIntensity = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0", ClampMax = "100"))
float StructureFireIntensity = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0"))
float ActiveFireSpreadRadius = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0"))
float BaseFireSpreadRadius = 250.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0"))
float MaxFireSpreadRadius = 3500.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0"))
float FireSpreadIntensityPerSecond = 0.18f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Spread", meta = (ClampMin = "0"))
float FireSuppressionPressure = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Weather", meta = (ClampMin = "1"))
float RainFuelDrainMultiplier = 1.5f;
@@ -219,4 +243,7 @@ protected:
float GetVegetationIgnitionWeatherMultiplier() const;
void UpdateStructureIgnitionRisk(float DeltaSeconds);
float GetStructureFuelScoreNearFire() const;
void UpdateServerAuthoritativeFireSpread(float DeltaSeconds);
float GetFireSpreadWeatherMultiplier() const;
float GetActiveBurningFuelScore() const;
};