384 lines
11 KiB
C++
384 lines
11 KiB
C++
// Copyright Pacificao. All Rights Reserved.
|
|
|
|
#include "AgrarianSurvivalComponent.h"
|
|
#include "AgrarianGameState.h"
|
|
#include "AgrarianPerformanceStats.h"
|
|
#include "AgrarianShelterActor.h"
|
|
#include "AgrarianWeatherExposureZone.h"
|
|
#include "Components/BoxComponent.h"
|
|
#include "Engine/World.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
|
|
|
UAgrarianSurvivalComponent::UAgrarianSurvivalComponent()
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
SetIsReplicatedByDefault(true);
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
ClampSurvival();
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AgrarianSurvivalTick);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianSurvivalTick);
|
|
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
|
|
if (!GetOwner() || !GetOwner()->HasAuthority() || !IsAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float Minutes = DeltaTime / 60.0f;
|
|
Survival.Hunger -= HungerDecayPerMinute * Minutes;
|
|
Survival.Thirst -= ThirstDecayPerMinute * Minutes;
|
|
Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;
|
|
CurrentWeatherProtection = CalculateCurrentWeatherProtection();
|
|
CurrentWeatherExposureMultiplier = CalculateCurrentWeatherExposureMultiplier();
|
|
CurrentWeatherTemperatureOffsetC = CalculateCurrentWeatherTemperatureOffsetC();
|
|
CareHistory.ShelterQuality = FMath::FInterpTo(CareHistory.ShelterQuality, CurrentWeatherProtection, DeltaTime, 0.02f);
|
|
|
|
if (Survival.Stamina <= LowStaminaExhaustionThreshold)
|
|
{
|
|
Survival.Exhaustion += ExhaustionGainPerLowStaminaSecond * DeltaTime;
|
|
}
|
|
else if (Survival.Hunger > 10.0f && Survival.Thirst > 10.0f && Survival.BodyTemperature >= 35.0f)
|
|
{
|
|
Survival.Exhaustion -= ExhaustionRecoveryPerSecond * DeltaTime;
|
|
}
|
|
|
|
if (Survival.SicknessSeverity > 0.0f)
|
|
{
|
|
Survival.Exhaustion += (Survival.SicknessSeverity / 100.0f) * 0.08f * DeltaTime;
|
|
CareHistory.IllnessBurden += (Survival.SicknessSeverity / 100.0f) * 0.001f * DeltaTime;
|
|
|
|
if (Survival.Hunger > 20.0f && Survival.Thirst > 20.0f && Survival.BodyTemperature >= 35.0f)
|
|
{
|
|
Survival.SicknessSeverity -= SicknessRecoveryPerSecond * DeltaTime * FMath::Max(0.25f, CareHistory.TreatmentQuality);
|
|
}
|
|
}
|
|
|
|
if (const UWorld* World = GetWorld())
|
|
{
|
|
if (const AAgrarianGameState* AgrarianGameState = World->GetGameState<AAgrarianGameState>())
|
|
{
|
|
const float ExposureProtectionMultiplier = 1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f);
|
|
const float EffectiveAmbientTemperatureC = AgrarianGameState->AmbientTemperatureC + CurrentWeatherTemperatureOffsetC;
|
|
const float ExposureDelta = (EffectiveAmbientTemperatureC - 18.0f) * 0.002f * DeltaTime * ExposureProtectionMultiplier * CurrentWeatherExposureMultiplier;
|
|
Survival.BodyTemperature += FMath::Clamp(ExposureDelta, -0.035f, 0.02f);
|
|
}
|
|
}
|
|
|
|
if (Survival.Hunger <= 0.0f)
|
|
{
|
|
Survival.Health -= StarvationDamagePerMinute * Minutes;
|
|
}
|
|
|
|
if (Survival.Thirst <= 0.0f)
|
|
{
|
|
Survival.Health -= DehydrationDamagePerMinute * Minutes;
|
|
}
|
|
|
|
if (Survival.BodyTemperature < 35.0f)
|
|
{
|
|
Survival.Health -= ColdDamagePerMinute * Minutes * (1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f)) * CurrentWeatherExposureMultiplier;
|
|
}
|
|
|
|
if (Survival.SicknessSeverity >= 60.0f)
|
|
{
|
|
Survival.Health -= SicknessDamagePerMinute * (Survival.SicknessSeverity / 100.0f) * Minutes;
|
|
}
|
|
|
|
ClampSurvival();
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
DOREPLIFETIME(UAgrarianSurvivalComponent, Survival);
|
|
DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);
|
|
DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherProtection);
|
|
DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherExposureMultiplier);
|
|
DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherTemperatureOffsetC);
|
|
}
|
|
|
|
bool UAgrarianSurvivalComponent::IsAlive() const
|
|
{
|
|
return Survival.Health > 0.0f;
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ApplyDamage(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Health -= FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::RestoreHealth(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Health += FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddFood(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Hunger += FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddWater(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Thirst += FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddWarmth(float DegreesCelsius)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.BodyTemperature += DegreesCelsius;
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddInjury(float Severity)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.InjurySeverity += FMath::Max(0.0f, Severity);
|
|
CareHistory.InjuryBurden += FMath::Max(0.0f, Severity) / 100.0f;
|
|
Survival.Health -= Severity * 5.0f;
|
|
ClampSurvival();
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ReduceInjury(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.InjurySeverity -= FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddSickness(float Severity)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
const float PositiveSeverity = FMath::Max(0.0f, Severity);
|
|
Survival.SicknessSeverity += PositiveSeverity;
|
|
CareHistory.IllnessBurden += PositiveSeverity / 100.0f;
|
|
ClampSurvival();
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ReduceSickness(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.SicknessSeverity -= FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival = SavedSurvival;
|
|
CareHistory = SavedCareHistory;
|
|
ClampSurvival();
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::SpendStamina(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
const float PositiveAmount = FMath::Max(0.0f, Amount);
|
|
Survival.Stamina -= PositiveAmount;
|
|
Survival.Exhaustion += PositiveAmount * 0.05f;
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::AddExhaustion(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Exhaustion += FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ReduceExhaustion(float Amount)
|
|
{
|
|
if (GetOwner() && GetOwner()->HasAuthority())
|
|
{
|
|
Survival.Exhaustion -= FMath::Max(0.0f, Amount);
|
|
ClampSurvival();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
}
|
|
|
|
float UAgrarianSurvivalComponent::CalculateCurrentWeatherProtection() const
|
|
{
|
|
const AActor* Owner = GetOwner();
|
|
if (!Owner)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
TArray<AActor*> OverlappingShelterActors;
|
|
Owner->GetOverlappingActors(OverlappingShelterActors, AAgrarianShelterActor::StaticClass());
|
|
|
|
float BestProtection = 0.0f;
|
|
for (const AActor* Actor : OverlappingShelterActors)
|
|
{
|
|
const AAgrarianShelterActor* Shelter = Cast<AAgrarianShelterActor>(Actor);
|
|
if (!Shelter || !Shelter->ProtectionVolume || !Shelter->ProtectionVolume->IsOverlappingActor(Owner))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
BestProtection = FMath::Max(BestProtection, FMath::Clamp(Shelter->WeatherProtection, 0.0f, 1.0f));
|
|
}
|
|
|
|
return BestProtection;
|
|
}
|
|
|
|
float UAgrarianSurvivalComponent::CalculateCurrentWeatherExposureMultiplier() const
|
|
{
|
|
const AActor* Owner = GetOwner();
|
|
if (!Owner)
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
TArray<AActor*> OverlappingZoneActors;
|
|
Owner->GetOverlappingActors(OverlappingZoneActors, AAgrarianWeatherExposureZone::StaticClass());
|
|
|
|
float StrongestMultiplierDelta = 0.0f;
|
|
for (const AActor* Actor : OverlappingZoneActors)
|
|
{
|
|
const AAgrarianWeatherExposureZone* Zone = Cast<AAgrarianWeatherExposureZone>(Actor);
|
|
if (!Zone || !Zone->ExposureVolume || !Zone->ExposureVolume->IsOverlappingActor(Owner))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float ZoneDelta = FMath::Clamp(Zone->ExposureMultiplier, 0.0f, 3.0f) - 1.0f;
|
|
if (FMath::Abs(ZoneDelta) > FMath::Abs(StrongestMultiplierDelta))
|
|
{
|
|
StrongestMultiplierDelta = ZoneDelta;
|
|
}
|
|
}
|
|
|
|
return FMath::Clamp(1.0f + StrongestMultiplierDelta, 0.0f, 3.0f);
|
|
}
|
|
|
|
float UAgrarianSurvivalComponent::CalculateCurrentWeatherTemperatureOffsetC() const
|
|
{
|
|
const AActor* Owner = GetOwner();
|
|
if (!Owner)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
TArray<AActor*> OverlappingZoneActors;
|
|
Owner->GetOverlappingActors(OverlappingZoneActors, AAgrarianWeatherExposureZone::StaticClass());
|
|
|
|
float StrongestOffset = 0.0f;
|
|
for (const AActor* Actor : OverlappingZoneActors)
|
|
{
|
|
const AAgrarianWeatherExposureZone* Zone = Cast<AAgrarianWeatherExposureZone>(Actor);
|
|
if (!Zone || !Zone->ExposureVolume || !Zone->ExposureVolume->IsOverlappingActor(Owner))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float ZoneOffset = FMath::Clamp(Zone->TemperatureOffsetC, -20.0f, 20.0f);
|
|
if (FMath::Abs(ZoneOffset) > FMath::Abs(StrongestOffset))
|
|
{
|
|
StrongestOffset = ZoneOffset;
|
|
}
|
|
}
|
|
|
|
return StrongestOffset;
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::OnRep_Survival()
|
|
{
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::OnRep_CareHistory()
|
|
{
|
|
ClampCareHistory();
|
|
BroadcastSurvivalChanged();
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ClampSurvival()
|
|
{
|
|
Survival.Health = FMath::Clamp(Survival.Health, 0.0f, 100.0f);
|
|
Survival.Stamina = FMath::Clamp(Survival.Stamina, 0.0f, 100.0f);
|
|
Survival.Exhaustion = FMath::Clamp(Survival.Exhaustion, 0.0f, 100.0f);
|
|
Survival.Hunger = FMath::Clamp(Survival.Hunger, 0.0f, 100.0f);
|
|
Survival.Thirst = FMath::Clamp(Survival.Thirst, 0.0f, 100.0f);
|
|
Survival.BodyTemperature = FMath::Clamp(Survival.BodyTemperature, 30.0f, 42.0f);
|
|
Survival.InjurySeverity = FMath::Clamp(Survival.InjurySeverity, 0.0f, 100.0f);
|
|
Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::ClampCareHistory()
|
|
{
|
|
CareHistory.NutritionQuality = FMath::Clamp(CareHistory.NutritionQuality, 0.0f, 1.0f);
|
|
CareHistory.IllnessBurden = FMath::Clamp(CareHistory.IllnessBurden, 0.0f, 1.0f);
|
|
CareHistory.InjuryBurden = FMath::Clamp(CareHistory.InjuryBurden, 0.0f, 1.0f);
|
|
CareHistory.SleepQuality = FMath::Clamp(CareHistory.SleepQuality, 0.0f, 1.0f);
|
|
CareHistory.ShelterQuality = FMath::Clamp(CareHistory.ShelterQuality, 0.0f, 1.0f);
|
|
CareHistory.StressBurden = FMath::Clamp(CareHistory.StressBurden, 0.0f, 1.0f);
|
|
CareHistory.WorkloadBurden = FMath::Clamp(CareHistory.WorkloadBurden, 0.0f, 1.0f);
|
|
CareHistory.TreatmentQuality = FMath::Clamp(CareHistory.TreatmentQuality, 0.0f, 1.0f);
|
|
}
|
|
|
|
void UAgrarianSurvivalComponent::BroadcastSurvivalChanged()
|
|
{
|
|
OnSurvivalChanged.Broadcast(Survival);
|
|
}
|