Complete early roadmap foundation and calendar helpers
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "AgrarianSurvivalComponent.h"
|
||||
#include "Engine/Canvas.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
void AAgrarianDebugHUD::DrawHUD()
|
||||
{
|
||||
@@ -22,6 +23,7 @@ void AAgrarianDebugHUD::DrawHUD()
|
||||
}
|
||||
|
||||
DrawInteractionPrompt(AgrarianCharacter);
|
||||
DrawCriticalStats(AgrarianCharacter->GetSurvivalComponent());
|
||||
|
||||
if (bShowDebugHUD)
|
||||
{
|
||||
@@ -29,6 +31,7 @@ void AAgrarianDebugHUD::DrawHUD()
|
||||
constexpr float X = 32.0f;
|
||||
|
||||
DrawLine(TEXT("AGRARIAN DEV HUD"), X, Y, FColor(160, 220, 140));
|
||||
DrawPlayerStatus(AgrarianCharacter, X, Y);
|
||||
DrawSurvival(AgrarianCharacter->GetSurvivalComponent(), X, Y);
|
||||
DrawInventory(AgrarianCharacter->GetInventoryComponent(), X, Y);
|
||||
}
|
||||
@@ -51,6 +54,108 @@ void AAgrarianDebugHUD::DrawInteractionPrompt(const AAgrarianGameCharacter* Agra
|
||||
DrawText(Prompt, FColor(245, 245, 225), X, Y, nullptr, PromptTextScale, true);
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent)
|
||||
{
|
||||
if (!bShowCriticalStatsHUD || !SurvivalComponent || !Canvas)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
|
||||
float Y = FMath::Max(32.0f, Canvas->ClipY - (178.0f * CriticalStatsTextScale));
|
||||
constexpr float X = 32.0f;
|
||||
|
||||
const FColor CriticalColor(230, 90, 70);
|
||||
const FColor WarningColor(245, 190, 80);
|
||||
const FColor StableColor(225, 235, 220);
|
||||
const FColor HeaderColor(160, 220, 140);
|
||||
|
||||
auto StatusColor = [CriticalColor, WarningColor, StableColor](float Value, bool bHigherIsWorse = false)
|
||||
{
|
||||
if (bHigherIsWorse)
|
||||
{
|
||||
if (Value >= 75.0f)
|
||||
{
|
||||
return CriticalColor;
|
||||
}
|
||||
if (Value >= 40.0f)
|
||||
{
|
||||
return WarningColor;
|
||||
}
|
||||
return StableColor;
|
||||
}
|
||||
|
||||
if (Value <= 20.0f)
|
||||
{
|
||||
return CriticalColor;
|
||||
}
|
||||
if (Value <= 45.0f)
|
||||
{
|
||||
return WarningColor;
|
||||
}
|
||||
return StableColor;
|
||||
};
|
||||
|
||||
DrawScaledLine(TEXT("SURVIVAL"), X, Y, CriticalStatsTextScale, HeaderColor);
|
||||
DrawScaledLine(FString::Printf(TEXT("Health %3.0f"), Survival.Health), X, Y, CriticalStatsTextScale, StatusColor(Survival.Health));
|
||||
DrawScaledLine(FString::Printf(TEXT("Stamina %3.0f"), Survival.Stamina), X, Y, CriticalStatsTextScale, StatusColor(Survival.Stamina));
|
||||
DrawScaledLine(FString::Printf(TEXT("Food %3.0f"), Survival.Hunger), X, Y, CriticalStatsTextScale, StatusColor(Survival.Hunger));
|
||||
DrawScaledLine(FString::Printf(TEXT("Water %3.0f"), Survival.Thirst), X, Y, CriticalStatsTextScale, StatusColor(Survival.Thirst));
|
||||
DrawScaledLine(FString::Printf(TEXT("Temp %4.1f C"), Survival.BodyTemperature), X, Y, CriticalStatsTextScale, Survival.BodyTemperature < 35.0f ? CriticalColor : StableColor);
|
||||
DrawScaledLine(FString::Printf(TEXT("Exhaust %3.0f"), Survival.Exhaustion), X, Y, CriticalStatsTextScale, StatusColor(Survival.Exhaustion, true));
|
||||
DrawScaledLine(FString::Printf(TEXT("Injury %3.0f"), Survival.InjurySeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.InjurySeverity, true));
|
||||
DrawScaledLine(FString::Printf(TEXT("Sickness %3.0f"), Survival.SicknessSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SicknessSeverity, true));
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawPlayerStatus(const AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y)
|
||||
{
|
||||
if (!AgrarianCharacter)
|
||||
{
|
||||
DrawLine(TEXT("Player: unavailable"), X, Y, FColor::Red);
|
||||
return;
|
||||
}
|
||||
|
||||
const UCharacterMovementComponent* MovementComponent = AgrarianCharacter->GetCharacterMovement();
|
||||
const FVector Location = AgrarianCharacter->GetActorLocation();
|
||||
const float HorizontalSpeed = AgrarianCharacter->GetVelocity().Size2D();
|
||||
const FString MovementMode = MovementComponent
|
||||
? UEnum::GetValueAsString(MovementComponent->MovementMode)
|
||||
: TEXT("Unavailable");
|
||||
|
||||
DrawLine(FString::Printf(
|
||||
TEXT("Role: %s | Local: %s"),
|
||||
AgrarianCharacter->HasAuthority() ? TEXT("Authority") : TEXT("Simulated/Autonomous"),
|
||||
AgrarianCharacter->IsLocallyControlled() ? TEXT("yes") : TEXT("no")),
|
||||
X,
|
||||
Y,
|
||||
FColor(160, 220, 140));
|
||||
DrawLine(FString::Printf(TEXT("Location: X %.0f Y %.0f Z %.0f"), Location.X, Location.Y, Location.Z), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Speed: %.0f uu/s | Mode: %s"), HorizontalSpeed, *MovementMode), X, Y);
|
||||
DrawLine(FString::Printf(
|
||||
TEXT("Stance: %s | Sprint: %s | Camera: %s"),
|
||||
AgrarianCharacter->IsProne() ? TEXT("Prone") : (AgrarianCharacter->bIsCrouched ? TEXT("Crouched") : TEXT("Standing")),
|
||||
AgrarianCharacter->IsSprinting() ? TEXT("yes") : TEXT("no"),
|
||||
AgrarianCharacter->IsFirstPersonCamera() ? TEXT("First") : TEXT("Third")),
|
||||
X,
|
||||
Y);
|
||||
DrawLine(FString::Printf(
|
||||
TEXT("Move Mult: %.2f | Carry: %.1f"),
|
||||
AgrarianCharacter->GetCurrentMovementSpeedMultiplier(),
|
||||
AgrarianCharacter->GetCurrentCarryWeight()),
|
||||
X,
|
||||
Y);
|
||||
DrawLine(FString::Printf(
|
||||
TEXT("Age %.1f | Cond %.2f | Str %.2f | End %.2f | Terrain %.2f"),
|
||||
AgrarianCharacter->GetAgeYears(),
|
||||
AgrarianCharacter->GetPhysicalConditionMultiplier(),
|
||||
AgrarianCharacter->GetStrengthMultiplier(),
|
||||
AgrarianCharacter->GetEnduranceMultiplier(),
|
||||
AgrarianCharacter->GetTerrainMovementMultiplier()),
|
||||
X,
|
||||
Y);
|
||||
Y += 10.0f * TextScale;
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y)
|
||||
{
|
||||
if (!SurvivalComponent)
|
||||
@@ -62,10 +167,15 @@ void AAgrarianDebugHUD::DrawSurvival(const UAgrarianSurvivalComponent* SurvivalC
|
||||
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
|
||||
DrawLine(FString::Printf(TEXT("Health: %.0f"), Survival.Health), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Stamina: %.0f"), Survival.Stamina), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Exhaust: %.0f"), Survival.Exhaustion), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Hunger: %.0f"), Survival.Hunger), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Thirst: %.0f"), Survival.Thirst), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Temp: %.1f C"), Survival.BodyTemperature), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Injury: %.0f"), Survival.InjurySeverity), X, Y);
|
||||
DrawLine(FString::Printf(TEXT("Sick: %.0f"), Survival.SicknessSeverity), X, Y);
|
||||
const FAgrarianCareHistorySnapshot& Care = SurvivalComponent->CareHistory;
|
||||
DrawLine(FString::Printf(TEXT("Care N/S/T: %.2f %.2f %.2f"), Care.NutritionQuality, Care.SleepQuality, Care.TreatmentQuality), X, Y, FColor::Silver);
|
||||
DrawLine(FString::Printf(TEXT("Burden I/I/S/W: %.2f %.2f %.2f %.2f"), Care.IllnessBurden, Care.InjuryBurden, Care.StressBurden, Care.WorkloadBurden), X, Y, FColor::Silver);
|
||||
Y += 10.0f * TextScale;
|
||||
}
|
||||
|
||||
@@ -94,6 +204,11 @@ void AAgrarianDebugHUD::DrawInventory(const UAgrarianInventoryComponent* Invento
|
||||
|
||||
void AAgrarianDebugHUD::DrawLine(const FString& Text, float X, float& Y, const FColor& Color)
|
||||
{
|
||||
DrawText(Text, Color, X, Y, nullptr, TextScale, false);
|
||||
Y += 18.0f * TextScale;
|
||||
DrawScaledLine(Text, X, Y, TextScale, Color);
|
||||
}
|
||||
|
||||
void AAgrarianDebugHUD::DrawScaledLine(const FString& Text, float X, float& Y, float Scale, const FColor& Color)
|
||||
{
|
||||
DrawText(Text, Color, X, Y, nullptr, Scale, false);
|
||||
Y += 18.0f * Scale;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,15 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
|
||||
bool bShowDebugHUD = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
|
||||
bool bShowCriticalStatsHUD = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
|
||||
float TextScale = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25"))
|
||||
float CriticalStatsTextScale = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD")
|
||||
bool bShowInteractionPrompt = true;
|
||||
|
||||
@@ -31,7 +37,10 @@ public:
|
||||
|
||||
protected:
|
||||
void DrawInteractionPrompt(const class AAgrarianGameCharacter* AgrarianCharacter);
|
||||
void DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent);
|
||||
void DrawPlayerStatus(const class AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y);
|
||||
void DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y);
|
||||
void DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y);
|
||||
void DrawLine(const FString& Text, float X, float& Y, const FColor& Color = FColor::White);
|
||||
void DrawScaledLine(const FString& Text, float X, float& Y, float Scale, const FColor& Color = FColor::White);
|
||||
};
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
AAgrarianGameCharacter::AAgrarianGameCharacter()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
bReplicates = true;
|
||||
SetReplicateMovement(true);
|
||||
SetNetUpdateFrequency(30.0f);
|
||||
SetMinNetUpdateFrequency(10.0f);
|
||||
|
||||
// Set size for collision capsule
|
||||
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
||||
@@ -286,6 +290,7 @@ bool AAgrarianGameCharacter::CanSprint() const
|
||||
&& SurvivalComponent->IsAlive()
|
||||
&& !bIsProne
|
||||
&& !bIsCrouched
|
||||
&& SurvivalComponent->Survival.Exhaustion < 85.0f
|
||||
&& SurvivalComponent->Survival.Stamina > MinSprintStamina;
|
||||
}
|
||||
|
||||
@@ -329,7 +334,19 @@ void AAgrarianGameCharacter::SetProne(bool bNewProne)
|
||||
|
||||
void AAgrarianGameCharacter::SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier)
|
||||
{
|
||||
TerrainMovementMultiplier = FMath::Clamp(NewTerrainMovementMultiplier, 0.25f, 1.25f);
|
||||
const float ClampedTerrainMovementMultiplier = FMath::Clamp(NewTerrainMovementMultiplier, 0.25f, 1.25f);
|
||||
if (!HasAuthority())
|
||||
{
|
||||
ServerSetTerrainMovementMultiplier(ClampedTerrainMovementMultiplier);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FMath::IsNearlyEqual(TerrainMovementMultiplier, ClampedTerrainMovementMultiplier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TerrainMovementMultiplier = ClampedTerrainMovementMultiplier;
|
||||
ApplyMovementSpeed();
|
||||
}
|
||||
|
||||
@@ -403,8 +420,16 @@ float AAgrarianGameCharacter::CalculateSurvivalMovementMultiplier() const
|
||||
FVector2D(0.0f, 100.0f),
|
||||
FVector2D(1.0f, 0.5f),
|
||||
Survival.InjurySeverity);
|
||||
const float SicknessMultiplier = FMath::GetMappedRangeValueClamped(
|
||||
FVector2D(0.0f, 100.0f),
|
||||
FVector2D(1.0f, 0.7f),
|
||||
Survival.SicknessSeverity);
|
||||
const float ExhaustionMultiplier = FMath::GetMappedRangeValueClamped(
|
||||
FVector2D(0.0f, 100.0f),
|
||||
FVector2D(1.0f, 0.55f),
|
||||
Survival.Exhaustion);
|
||||
|
||||
return HungerMultiplier * ThirstMultiplier * InjuryMultiplier;
|
||||
return HungerMultiplier * ThirstMultiplier * InjuryMultiplier * SicknessMultiplier * ExhaustionMultiplier;
|
||||
}
|
||||
|
||||
float AAgrarianGameCharacter::CalculateCarryWeightMovementMultiplier() const
|
||||
@@ -460,6 +485,11 @@ void AAgrarianGameCharacter::OnRep_ProneState()
|
||||
ApplyMovementSpeed();
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::OnRep_MovementModifierState()
|
||||
{
|
||||
ApplyMovementSpeed();
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::DoMove(float Right, float Forward)
|
||||
{
|
||||
if (GetController() != nullptr)
|
||||
@@ -585,6 +615,11 @@ void AAgrarianGameCharacter::ServerSetProne_Implementation(bool bNewProne)
|
||||
SetProne(bNewProne);
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::ServerSetTerrainMovementMultiplier_Implementation(float NewTerrainMovementMultiplier)
|
||||
{
|
||||
SetTerrainMovementMultiplier(NewTerrainMovementMultiplier);
|
||||
}
|
||||
|
||||
void AAgrarianGameCharacter::ServerInteract_Implementation(AActor* TargetActor)
|
||||
{
|
||||
if (!TargetActor || !TargetActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass()))
|
||||
|
||||
@@ -126,19 +126,19 @@ protected:
|
||||
float ProneSpeedMultiplier = 0.25f;
|
||||
|
||||
/** Age hook used by movement until full lifecycle/aging systems own it. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0"))
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0"))
|
||||
float AgeYears = 25.0f;
|
||||
|
||||
/** General physical condition scalar reserved for care, illness, sleep, and long-term state. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
|
||||
float PhysicalConditionMultiplier = 1.0f;
|
||||
|
||||
/** Strength scalar that mainly offsets carried-weight penalties. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
|
||||
float StrengthMultiplier = 1.0f;
|
||||
|
||||
/** Endurance scalar that improves stamina efficiency and movement resilience. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "2.0"))
|
||||
float EnduranceMultiplier = 1.0f;
|
||||
|
||||
/** Comfort carry capacity before speed penalties, in item-weight units. */
|
||||
@@ -150,7 +150,7 @@ protected:
|
||||
float HeavyCarryWeight = 60.0f;
|
||||
|
||||
/** Terrain hook for later surface/volume systems. Values below one slow the character. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_MovementModifierState, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0.25", ClampMax = "1.25"))
|
||||
float TerrainMovementMultiplier = 1.0f;
|
||||
|
||||
/** Third-person spring arm distance used when returning from first person. */
|
||||
@@ -238,6 +238,9 @@ protected:
|
||||
UFUNCTION()
|
||||
void OnRep_ProneState();
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_MovementModifierState();
|
||||
|
||||
public:
|
||||
|
||||
/** Handles move inputs from either controls or UI interfaces */
|
||||
@@ -292,6 +295,21 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement")
|
||||
float GetCurrentMovementSpeedMultiplier() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
|
||||
float GetAgeYears() const { return AgeYears; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
|
||||
float GetPhysicalConditionMultiplier() const { return PhysicalConditionMultiplier; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
|
||||
float GetStrengthMultiplier() const { return StrengthMultiplier; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
|
||||
float GetEnduranceMultiplier() const { return EnduranceMultiplier; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Modifiers")
|
||||
float GetTerrainMovementMultiplier() const { return TerrainMovementMultiplier; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Agrarian|Movement")
|
||||
void SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier);
|
||||
|
||||
@@ -307,6 +325,10 @@ public:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerSetProne(bool bNewProne);
|
||||
|
||||
/** Server-authoritative terrain movement modifier update. */
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerSetTerrainMovementMultiplier(float NewTerrainMovementMultiplier);
|
||||
|
||||
/** Refreshes local interactable focus and prompt text. */
|
||||
void UpdateInteractionPrompt();
|
||||
|
||||
|
||||
@@ -104,13 +104,15 @@ void AAgrarianGamePlayerController::AgrarianSurvival()
|
||||
|
||||
const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival;
|
||||
ClientMessage(FString::Printf(
|
||||
TEXT("Health %.1f | Stamina %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f"),
|
||||
TEXT("Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Sickness %.1f"),
|
||||
Survival.Health,
|
||||
Survival.Stamina,
|
||||
Survival.Exhaustion,
|
||||
Survival.Hunger,
|
||||
Survival.Thirst,
|
||||
Survival.BodyTemperature,
|
||||
Survival.InjurySeverity));
|
||||
Survival.InjurySeverity,
|
||||
Survival.SicknessSeverity));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::AgrarianHeal()
|
||||
@@ -167,8 +169,9 @@ void AAgrarianGamePlayerController::ServerAgrarianLoadWorld_Implementation()
|
||||
|
||||
Persistence->RegisterWorldActorClass(TEXT("primitive_shelter"), AAgrarianShelterActor::StaticClass());
|
||||
const UAgrarianSaveGame* SaveGame = Persistence->LoadOrCreateSave();
|
||||
const int32 RestoredPlayerCount = Persistence->RestorePlayers(SaveGame);
|
||||
const int32 RestoredCount = Persistence->RestoreWorldActors(SaveGame);
|
||||
ClientMessage(FString::Printf(TEXT("Agrarian world loaded. Restored actors: %d."), RestoredCount));
|
||||
ClientMessage(FString::Printf(TEXT("Agrarian world loaded. Restored players: %d. Restored actors: %d."), RestoredPlayerCount, RestoredCount));
|
||||
}
|
||||
|
||||
void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
|
||||
@@ -184,6 +187,8 @@ void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation()
|
||||
SurvivalComponent->RestoreHealth(100.0f);
|
||||
SurvivalComponent->AddFood(100.0f);
|
||||
SurvivalComponent->AddWater(100.0f);
|
||||
SurvivalComponent->ReduceExhaustion(100.0f);
|
||||
SurvivalComponent->ReduceSickness(100.0f);
|
||||
SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature);
|
||||
ClientMessage(TEXT("Agrarian survival restored."));
|
||||
}
|
||||
|
||||
@@ -3,10 +3,52 @@
|
||||
#include "AgrarianGameState.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
int32 NormalizeCalendarDay(int32 DayOfYear, int32 DaysPerYear)
|
||||
{
|
||||
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerYear);
|
||||
const int32 ZeroBasedDay = ((DayOfYear - 1) % SafeDaysPerYear + SafeDaysPerYear) % SafeDaysPerYear;
|
||||
return ZeroBasedDay + 1;
|
||||
}
|
||||
|
||||
bool IsDayInRange(int32 DayOfYear, int32 StartDay, int32 EndDay, int32 DaysPerYear)
|
||||
{
|
||||
const int32 Day = NormalizeCalendarDay(DayOfYear, DaysPerYear);
|
||||
const int32 Start = NormalizeCalendarDay(StartDay, DaysPerYear);
|
||||
const int32 End = NormalizeCalendarDay(EndDay, DaysPerYear);
|
||||
|
||||
if (Start <= End)
|
||||
{
|
||||
return Day >= Start && Day <= End;
|
||||
}
|
||||
|
||||
return Day >= Start || Day <= End;
|
||||
}
|
||||
}
|
||||
|
||||
AAgrarianGameState::AAgrarianGameState()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
bReplicates = true;
|
||||
ActiveGrowingSeason.TileId = ActiveSolarTileId;
|
||||
ActiveGrowingSeason.GrowingZoneLabel = TEXT("USDA 10a");
|
||||
ActiveGrowingSeason.GrowingSeasonStartDay = 46;
|
||||
ActiveGrowingSeason.GrowingSeasonEndDay = 350;
|
||||
ActiveGrowingSeason.FrostFreeDays = 305;
|
||||
ActiveGrowingSeason.MinAverageGrowingTempC = 7.0f;
|
||||
ActiveGrowingSeason.CropSafetyBufferDays = 14;
|
||||
ActiveGrowingSeason.ClimateProfile = TEXT("coastal_mediterranean_mild");
|
||||
}
|
||||
|
||||
void AAgrarianGameState::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
if (HasAuthority())
|
||||
{
|
||||
UpdateSolarTimes();
|
||||
}
|
||||
}
|
||||
|
||||
void AAgrarianGameState::Tick(float DeltaSeconds)
|
||||
@@ -22,6 +64,13 @@ void AAgrarianGameState::Tick(float DeltaSeconds)
|
||||
while (WorldHours >= 24.0f)
|
||||
{
|
||||
WorldHours -= 24.0f;
|
||||
const int32 PreviousDayOfYear = ActiveDayOfYear;
|
||||
ActiveDayOfYear = (ActiveDayOfYear % FMath::Max(1, DaysPerAgrarianYear)) + 1;
|
||||
if (ActiveDayOfYear < PreviousDayOfYear)
|
||||
{
|
||||
++ActiveYear;
|
||||
}
|
||||
UpdateSolarTimes();
|
||||
}
|
||||
|
||||
UpdateAmbientTemperature();
|
||||
@@ -33,10 +82,29 @@ void AAgrarianGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& O
|
||||
DOREPLIFETIME(AAgrarianGameState, WorldHours);
|
||||
DOREPLIFETIME(AAgrarianGameState, Weather);
|
||||
DOREPLIFETIME(AAgrarianGameState, AmbientTemperatureC);
|
||||
DOREPLIFETIME(AAgrarianGameState, DaysPerAgrarianYear);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveSolarTileId);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveTileLatitude);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveTileLongitude);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveTileTimeZoneId);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveTileUtcOffsetHours);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveDayOfYear);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveYear);
|
||||
DOREPLIFETIME(AAgrarianGameState, ActiveGrowingSeason);
|
||||
DOREPLIFETIME(AAgrarianGameState, bHasActiveTileSolarData);
|
||||
DOREPLIFETIME(AAgrarianGameState, SunriseHourLocal);
|
||||
DOREPLIFETIME(AAgrarianGameState, SunsetHourLocal);
|
||||
DOREPLIFETIME(AAgrarianGameState, SolarNoonHourLocal);
|
||||
DOREPLIFETIME(AAgrarianGameState, DayLengthHours);
|
||||
}
|
||||
|
||||
bool AAgrarianGameState::IsNight() const
|
||||
{
|
||||
if (bHasActiveTileSolarData)
|
||||
{
|
||||
return WorldHours < SunriseHourLocal || WorldHours > SunsetHourLocal;
|
||||
}
|
||||
|
||||
return WorldHours < 6.0f || WorldHours > 20.0f;
|
||||
}
|
||||
|
||||
@@ -50,14 +118,270 @@ void AAgrarianGameState::SetWeather(EAgrarianWeatherType NewWeather)
|
||||
}
|
||||
}
|
||||
|
||||
bool AAgrarianGameState::ConfigureActiveSolarTile(FName TileId, float Latitude, float Longitude, const FString& TimeZoneId, float UtcOffsetHours)
|
||||
{
|
||||
if (!HasAuthority() || TileId == NAME_None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ActiveSolarTileId = TileId;
|
||||
ActiveTileLatitude = FMath::Clamp(Latitude, -90.0f, 90.0f);
|
||||
ActiveTileLongitude = FMath::Clamp(Longitude, -180.0f, 180.0f);
|
||||
ActiveTileTimeZoneId = TimeZoneId;
|
||||
ActiveTileUtcOffsetHours = FMath::Clamp(UtcOffsetHours, -12.0f, 14.0f);
|
||||
ActiveGrowingSeason.TileId = TileId;
|
||||
UpdateSolarTimes();
|
||||
UpdateAmbientTemperature();
|
||||
return bHasActiveTileSolarData;
|
||||
}
|
||||
|
||||
FAgrarianCalendarSnapshot AAgrarianGameState::GetCalendarSnapshot() const
|
||||
{
|
||||
FAgrarianCalendarSnapshot Snapshot;
|
||||
Snapshot.DayOfYear = NormalizeCalendarDay(ActiveDayOfYear, DaysPerAgrarianYear);
|
||||
Snapshot.Year = FMath::Max(1, ActiveYear);
|
||||
Snapshot.AbsoluteDay = GetAbsoluteAgrarianDay();
|
||||
Snapshot.DayOfSeason = GetDayOfSeason(Snapshot.DayOfYear);
|
||||
Snapshot.Season = GetSeasonForDay(Snapshot.DayOfYear);
|
||||
Snapshot.HourOfDay = FMath::Clamp(WorldHours, 0.0f, 24.0f);
|
||||
Snapshot.bInsideGrowingSeason = IsDayInsideActiveGrowingSeason(Snapshot.DayOfYear);
|
||||
Snapshot.GrowingSeasonDaysRemaining = GetDaysRemainingInActiveGrowingSeason(Snapshot.DayOfYear);
|
||||
return Snapshot;
|
||||
}
|
||||
|
||||
int32 AAgrarianGameState::GetAbsoluteAgrarianDay() const
|
||||
{
|
||||
const int32 SafeYear = FMath::Max(1, ActiveYear);
|
||||
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
|
||||
return ((SafeYear - 1) * SafeDaysPerYear) + NormalizeCalendarDay(ActiveDayOfYear, SafeDaysPerYear);
|
||||
}
|
||||
|
||||
EAgrarianSeason AAgrarianGameState::GetSeasonForDay(int32 DayOfYear) const
|
||||
{
|
||||
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
|
||||
const int32 SeasonalDay = ActiveTileLatitude < 0.0f
|
||||
? NormalizeCalendarDay(DayOfYear + (SafeDaysPerYear / 2), SafeDaysPerYear)
|
||||
: NormalizeCalendarDay(DayOfYear, SafeDaysPerYear);
|
||||
|
||||
if (SeasonalDay >= 355 || SeasonalDay < 80)
|
||||
{
|
||||
return EAgrarianSeason::Winter;
|
||||
}
|
||||
if (SeasonalDay < 172)
|
||||
{
|
||||
return EAgrarianSeason::Spring;
|
||||
}
|
||||
if (SeasonalDay < 264)
|
||||
{
|
||||
return EAgrarianSeason::Summer;
|
||||
}
|
||||
return EAgrarianSeason::Autumn;
|
||||
}
|
||||
|
||||
int32 AAgrarianGameState::GetDayOfSeason(int32 DayOfYear) const
|
||||
{
|
||||
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
|
||||
const int32 SeasonalDay = ActiveTileLatitude < 0.0f
|
||||
? NormalizeCalendarDay(DayOfYear + (SafeDaysPerYear / 2), SafeDaysPerYear)
|
||||
: NormalizeCalendarDay(DayOfYear, SafeDaysPerYear);
|
||||
|
||||
switch (GetSeasonForDay(DayOfYear))
|
||||
{
|
||||
case EAgrarianSeason::Spring:
|
||||
return SeasonalDay - 79;
|
||||
case EAgrarianSeason::Summer:
|
||||
return SeasonalDay - 171;
|
||||
case EAgrarianSeason::Autumn:
|
||||
return SeasonalDay - 263;
|
||||
default:
|
||||
return SeasonalDay >= 355 ? SeasonalDay - 354 : SeasonalDay + 12;
|
||||
}
|
||||
}
|
||||
|
||||
float AAgrarianGameState::ConvertAgrarianDaysToRealHours(float AgrarianDays) const
|
||||
{
|
||||
const float SafeGameHoursPerRealMinute = FMath::Max(0.001f, GameHoursPerRealMinute);
|
||||
return FMath::Max(0.0f, AgrarianDays) * (24.0f / SafeGameHoursPerRealMinute) / 60.0f;
|
||||
}
|
||||
|
||||
float AAgrarianGameState::ConvertRealHoursToAgrarianDays(float RealHours) const
|
||||
{
|
||||
return (FMath::Max(0.0f, RealHours) * 60.0f * FMath::Max(0.001f, GameHoursPerRealMinute)) / 24.0f;
|
||||
}
|
||||
|
||||
float AAgrarianGameState::GetLongTaskProgress(int32 StartAbsoluteDay, float StartHourOfDay, float DurationAgrarianDays) const
|
||||
{
|
||||
const float SafeDuration = FMath::Max(0.001f, DurationAgrarianDays);
|
||||
const float CurrentAgrarianDay = static_cast<float>(GetAbsoluteAgrarianDay() - 1) + (WorldHours / 24.0f);
|
||||
const float StartAgrarianDay = static_cast<float>(FMath::Max(1, StartAbsoluteDay) - 1)
|
||||
+ (FMath::Clamp(StartHourOfDay, 0.0f, 24.0f) / 24.0f);
|
||||
return FMath::Clamp((CurrentAgrarianDay - StartAgrarianDay) / SafeDuration, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool AAgrarianGameState::IsDayInsideActiveGrowingSeason(int32 DayOfYear) const
|
||||
{
|
||||
return IsDayInRange(
|
||||
DayOfYear,
|
||||
ActiveGrowingSeason.GrowingSeasonStartDay,
|
||||
ActiveGrowingSeason.GrowingSeasonEndDay,
|
||||
DaysPerAgrarianYear);
|
||||
}
|
||||
|
||||
int32 AAgrarianGameState::GetDaysRemainingInActiveGrowingSeason(int32 PlantDayOfYear) const
|
||||
{
|
||||
const int32 SafeDaysPerYear = FMath::Max(1, DaysPerAgrarianYear);
|
||||
const int32 Day = NormalizeCalendarDay(PlantDayOfYear, SafeDaysPerYear);
|
||||
const int32 Start = NormalizeCalendarDay(ActiveGrowingSeason.GrowingSeasonStartDay, SafeDaysPerYear);
|
||||
const int32 End = NormalizeCalendarDay(ActiveGrowingSeason.GrowingSeasonEndDay, SafeDaysPerYear);
|
||||
|
||||
if (Start <= End)
|
||||
{
|
||||
if (Day < Start)
|
||||
{
|
||||
return End - Start + 1;
|
||||
}
|
||||
if (Day > End)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return End - Day + 1;
|
||||
}
|
||||
|
||||
if (Day >= Start)
|
||||
{
|
||||
return (SafeDaysPerYear - Day + 1) + End;
|
||||
}
|
||||
if (Day <= End)
|
||||
{
|
||||
return End - Day + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FAgrarianCropSeasonAssessment AAgrarianGameState::AssessCropForActiveGrowingSeason(int32 CropMaturityDays, int32 PlantDayOfYear) const
|
||||
{
|
||||
FAgrarianCropSeasonAssessment Assessment;
|
||||
Assessment.DaysAvailable = GetDaysRemainingInActiveGrowingSeason(PlantDayOfYear);
|
||||
Assessment.SafetyBufferDays = FMath::Max(0, ActiveGrowingSeason.CropSafetyBufferDays);
|
||||
Assessment.DaysRequired = FMath::Max(0, CropMaturityDays) + Assessment.SafetyBufferDays;
|
||||
Assessment.GrowingZoneLabel = ActiveGrowingSeason.GrowingZoneLabel;
|
||||
Assessment.bCanPlantToday = false;
|
||||
|
||||
if (CropMaturityDays <= 0)
|
||||
{
|
||||
Assessment.Fit = EAgrarianCropSeasonFit::Unknown;
|
||||
Assessment.Reason = FText::FromString(TEXT("Crop maturity days must be greater than zero."));
|
||||
return Assessment;
|
||||
}
|
||||
if (!IsDayInsideActiveGrowingSeason(PlantDayOfYear))
|
||||
{
|
||||
Assessment.Fit = EAgrarianCropSeasonFit::OutOfSeason;
|
||||
Assessment.Reason = FText::FromString(TEXT("Planting day is outside the active tile growing season."));
|
||||
return Assessment;
|
||||
}
|
||||
if (Assessment.DaysRequired > ActiveGrowingSeason.FrostFreeDays)
|
||||
{
|
||||
Assessment.Fit = EAgrarianCropSeasonFit::TooLong;
|
||||
Assessment.Reason = FText::FromString(TEXT("Crop maturity is too long for this tile's frost-free growing window."));
|
||||
return Assessment;
|
||||
}
|
||||
if (Assessment.DaysRequired > Assessment.DaysAvailable)
|
||||
{
|
||||
Assessment.Fit = EAgrarianCropSeasonFit::Marginal;
|
||||
Assessment.Reason = FText::FromString(TEXT("Crop can grow in this zone, but this planting date is too late for a reliable harvest."));
|
||||
return Assessment;
|
||||
}
|
||||
|
||||
Assessment.Fit = EAgrarianCropSeasonFit::FitsSeason;
|
||||
Assessment.bCanPlantToday = true;
|
||||
Assessment.Reason = FText::FromString(TEXT("Crop maturity fits the active tile growing season."));
|
||||
return Assessment;
|
||||
}
|
||||
|
||||
void AAgrarianGameState::OnRep_Weather()
|
||||
{
|
||||
UpdateAmbientTemperature();
|
||||
}
|
||||
|
||||
void AAgrarianGameState::UpdateSolarTimes()
|
||||
{
|
||||
if (ActiveSolarTileId == NAME_None)
|
||||
{
|
||||
bHasActiveTileSolarData = false;
|
||||
SunriseHourLocal = 6.0f;
|
||||
SunsetHourLocal = 20.0f;
|
||||
SolarNoonHourLocal = 13.0f;
|
||||
DayLengthHours = 14.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
const float ClampedLatitude = FMath::Clamp(ActiveTileLatitude, -89.8f, 89.8f);
|
||||
const float ClampedLongitude = FMath::Clamp(ActiveTileLongitude, -180.0f, 180.0f);
|
||||
const int32 ClampedDayOfYear = FMath::Clamp(ActiveDayOfYear, 1, 366);
|
||||
// NOAA approximate sunrise/sunset model; good enough for MVP regional light timing.
|
||||
const double Gamma = (2.0 * PI / 365.0) * (static_cast<double>(ClampedDayOfYear) - 1.0);
|
||||
const double EquationOfTimeMinutes = 229.18 * (
|
||||
0.000075
|
||||
+ 0.001868 * FMath::Cos(Gamma)
|
||||
- 0.032077 * FMath::Sin(Gamma)
|
||||
- 0.014615 * FMath::Cos(2.0 * Gamma)
|
||||
- 0.040849 * FMath::Sin(2.0 * Gamma));
|
||||
const double SolarDeclinationRadians =
|
||||
0.006918
|
||||
- 0.399912 * FMath::Cos(Gamma)
|
||||
+ 0.070257 * FMath::Sin(Gamma)
|
||||
- 0.006758 * FMath::Cos(2.0 * Gamma)
|
||||
+ 0.000907 * FMath::Sin(2.0 * Gamma)
|
||||
- 0.002697 * FMath::Cos(3.0 * Gamma)
|
||||
+ 0.00148 * FMath::Sin(3.0 * Gamma);
|
||||
|
||||
const double LatitudeRadians = FMath::DegreesToRadians(static_cast<double>(ClampedLatitude));
|
||||
const double SolarZenithRadians = FMath::DegreesToRadians(90.833);
|
||||
const double HourAngleArgument =
|
||||
(FMath::Cos(SolarZenithRadians) / (FMath::Cos(LatitudeRadians) * FMath::Cos(SolarDeclinationRadians)))
|
||||
- FMath::Tan(LatitudeRadians) * FMath::Tan(SolarDeclinationRadians);
|
||||
|
||||
if (HourAngleArgument <= -1.0 || HourAngleArgument >= 1.0)
|
||||
{
|
||||
bHasActiveTileSolarData = true;
|
||||
SolarNoonHourLocal = FMath::Fmod(12.0f + ActiveTileUtcOffsetHours - (ClampedLongitude / 15.0f), 24.0f);
|
||||
if (SolarNoonHourLocal < 0.0f)
|
||||
{
|
||||
SolarNoonHourLocal += 24.0f;
|
||||
}
|
||||
DayLengthHours = HourAngleArgument <= -1.0 ? 24.0f : 0.0f;
|
||||
SunriseHourLocal = DayLengthHours >= 24.0f ? 0.0f : SolarNoonHourLocal;
|
||||
SunsetHourLocal = DayLengthHours >= 24.0f ? 24.0f : SolarNoonHourLocal;
|
||||
return;
|
||||
}
|
||||
|
||||
const double HourAngleDegrees = FMath::RadiansToDegrees(FMath::Acos(HourAngleArgument));
|
||||
const double SolarNoonMinutes = 720.0 - (4.0 * ClampedLongitude) - EquationOfTimeMinutes + (ActiveTileUtcOffsetHours * 60.0);
|
||||
const double SunriseMinutes = SolarNoonMinutes - (HourAngleDegrees * 4.0);
|
||||
const double SunsetMinutes = SolarNoonMinutes + (HourAngleDegrees * 4.0);
|
||||
|
||||
auto NormalizeHour = [](double Minutes)
|
||||
{
|
||||
double Hours = FMath::Fmod(Minutes / 60.0, 24.0);
|
||||
if (Hours < 0.0)
|
||||
{
|
||||
Hours += 24.0;
|
||||
}
|
||||
return static_cast<float>(Hours);
|
||||
};
|
||||
|
||||
bHasActiveTileSolarData = true;
|
||||
SunriseHourLocal = NormalizeHour(SunriseMinutes);
|
||||
SunsetHourLocal = NormalizeHour(SunsetMinutes);
|
||||
SolarNoonHourLocal = NormalizeHour(SolarNoonMinutes);
|
||||
DayLengthHours = static_cast<float>((SunsetMinutes - SunriseMinutes) / 60.0);
|
||||
}
|
||||
|
||||
void AAgrarianGameState::UpdateAmbientTemperature()
|
||||
{
|
||||
const float DayWarmth = FMath::Sin((WorldHours / 24.0f) * 2.0f * PI - (PI * 0.5f)) * 8.0f;
|
||||
const float WarmestHour = bHasActiveTileSolarData ? FMath::Fmod(SolarNoonHourLocal + 3.0f, 24.0f) : 14.0f;
|
||||
const float DayWarmth = FMath::Sin(((WorldHours - WarmestHour) / 24.0f) * 2.0f * PI + (PI * 0.5f)) * 8.0f;
|
||||
float WeatherModifier = 0.0f;
|
||||
|
||||
switch (Weather)
|
||||
|
||||
@@ -15,6 +15,7 @@ class AAgrarianGameState : public AGameStateBase
|
||||
public:
|
||||
AAgrarianGameState();
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
@@ -24,21 +25,97 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|World", meta = (ClampMin = "0.1"))
|
||||
float GameHoursPerRealMinute = 0.1f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Calendar", meta = (ClampMin = "1", ClampMax = "366"))
|
||||
int32 DaysPerAgrarianYear = 366;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Weather, Category = "Agrarian|World")
|
||||
EAgrarianWeatherType Weather = EAgrarianWeatherType::Clear;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World")
|
||||
float AmbientTemperatureC = 12.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
FName ActiveSolarTileId = TEXT("gz_us_ca_pacifica_utm10n_e544_n4160");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float ActiveTileLatitude = 37.5925f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float ActiveTileLongitude = -122.4995f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
FString ActiveTileTimeZoneId = TEXT("America/Los_Angeles");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float ActiveTileUtcOffsetHours = -7.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar", meta = (ClampMin = "1", ClampMax = "366"))
|
||||
int32 ActiveDayOfYear = 172;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Calendar", meta = (ClampMin = "1"))
|
||||
int32 ActiveYear = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Growing Season")
|
||||
FAgrarianGrowingSeasonProfile ActiveGrowingSeason;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
bool bHasActiveTileSolarData = false;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float SunriseHourLocal = 6.0f;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float SunsetHourLocal = 20.0f;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float SolarNoonHourLocal = 13.0f;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Agrarian|World|Tile Solar")
|
||||
float DayLengthHours = 14.0f;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|World")
|
||||
bool IsNight() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|World")
|
||||
void SetWeather(EAgrarianWeatherType NewWeather);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|World|Tile Solar")
|
||||
bool ConfigureActiveSolarTile(FName TileId, float Latitude, float Longitude, const FString& TimeZoneId, float UtcOffsetHours);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
FAgrarianCalendarSnapshot GetCalendarSnapshot() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
int32 GetAbsoluteAgrarianDay() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
EAgrarianSeason GetSeasonForDay(int32 DayOfYear) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
int32 GetDayOfSeason(int32 DayOfYear) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
float ConvertAgrarianDaysToRealHours(float AgrarianDays) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
float ConvertRealHoursToAgrarianDays(float RealHours) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Calendar")
|
||||
float GetLongTaskProgress(int32 StartAbsoluteDay, float StartHourOfDay, float DurationAgrarianDays) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
|
||||
bool IsDayInsideActiveGrowingSeason(int32 DayOfYear) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
|
||||
int32 GetDaysRemainingInActiveGrowingSeason(int32 PlantDayOfYear) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Agrarian|World|Growing Season")
|
||||
FAgrarianCropSeasonAssessment AssessCropForActiveGrowingSeason(int32 CropMaturityDays, int32 PlantDayOfYear) const;
|
||||
|
||||
protected:
|
||||
UFUNCTION()
|
||||
void OnRep_Weather();
|
||||
|
||||
void UpdateSolarTimes();
|
||||
void UpdateAmbientTemperature();
|
||||
};
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright Pacificao. All Rights Reserved.
|
||||
|
||||
#include "AgrarianPersistenceSubsystem.h"
|
||||
#include "AgrarianGameCharacter.h"
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "AgrarianPersistentActorComponent.h"
|
||||
#include "AgrarianSaveGame.h"
|
||||
#include "AgrarianSurvivalComponent.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
UAgrarianSaveGame* UAgrarianPersistenceSubsystem::CreateEmptySave() const
|
||||
@@ -116,6 +120,87 @@ int32 UAgrarianPersistenceSubsystem::RestoreWorldActors(const UAgrarianSaveGame*
|
||||
return RestoredCount;
|
||||
}
|
||||
|
||||
int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame) const
|
||||
{
|
||||
if (!SaveGame)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
TArray<AAgrarianGameCharacter*> Players;
|
||||
FindAgrarianPlayers(Players);
|
||||
|
||||
SaveGame->Players.Reset();
|
||||
for (const AAgrarianGameCharacter* Character : Players)
|
||||
{
|
||||
const UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
|
||||
if (!Character || !SurvivalComponent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FAgrarianSavedPlayer SavedPlayer;
|
||||
SavedPlayer.PlayerId = GetPlayerPersistenceId(Character);
|
||||
SavedPlayer.Transform = Character->GetActorTransform();
|
||||
SavedPlayer.Survival = SurvivalComponent->Survival;
|
||||
SavedPlayer.CareHistory = SurvivalComponent->CareHistory;
|
||||
|
||||
if (const UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
|
||||
{
|
||||
SavedPlayer.Inventory = InventoryComponent->Items;
|
||||
}
|
||||
|
||||
SaveGame->Players.Add(SavedPlayer);
|
||||
}
|
||||
|
||||
return SaveGame->Players.Num();
|
||||
}
|
||||
|
||||
int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* SaveGame) const
|
||||
{
|
||||
if (!SaveGame)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
TArray<AAgrarianGameCharacter*> Players;
|
||||
FindAgrarianPlayers(Players);
|
||||
|
||||
int32 RestoredCount = 0;
|
||||
for (AAgrarianGameCharacter* Character : Players)
|
||||
{
|
||||
UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
|
||||
if (!Character || !SurvivalComponent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString PlayerId = GetPlayerPersistenceId(Character);
|
||||
const FAgrarianSavedPlayer* SavedPlayer = SaveGame->Players.FindByPredicate(
|
||||
[&PlayerId](const FAgrarianSavedPlayer& Candidate)
|
||||
{
|
||||
return Candidate.PlayerId == PlayerId;
|
||||
});
|
||||
|
||||
if (!SavedPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Character->SetActorTransform(SavedPlayer->Transform, false, nullptr, ETeleportType::TeleportPhysics);
|
||||
SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);
|
||||
|
||||
if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
|
||||
{
|
||||
InventoryComponent->Items = SavedPlayer->Inventory;
|
||||
}
|
||||
|
||||
RestoredCount++;
|
||||
}
|
||||
|
||||
return RestoredCount;
|
||||
}
|
||||
|
||||
bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
|
||||
{
|
||||
UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
|
||||
@@ -124,6 +209,7 @@ bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
|
||||
return false;
|
||||
}
|
||||
|
||||
CapturePlayers(SaveGame);
|
||||
CaptureWorldActors(SaveGame);
|
||||
return WriteSave(SaveGame);
|
||||
}
|
||||
@@ -152,3 +238,42 @@ void UAgrarianPersistenceSubsystem::FindPersistentComponents(TArray<UAgrarianPer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UAgrarianPersistenceSubsystem::FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const
|
||||
{
|
||||
OutPlayers.Reset();
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (TActorIterator<AAgrarianGameCharacter> ActorIt(World); ActorIt; ++ActorIt)
|
||||
{
|
||||
AAgrarianGameCharacter* Character = *ActorIt;
|
||||
if (Character && !Character->IsPendingKillPending())
|
||||
{
|
||||
OutPlayers.Add(Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const
|
||||
{
|
||||
if (!Character)
|
||||
{
|
||||
return TEXT("UnknownPlayer");
|
||||
}
|
||||
|
||||
if (const APlayerState* PlayerState = Character->GetPlayerState())
|
||||
{
|
||||
const FString PlayerName = PlayerState->GetPlayerName();
|
||||
if (!PlayerName.IsEmpty())
|
||||
{
|
||||
return PlayerName;
|
||||
}
|
||||
}
|
||||
|
||||
return Character->GetName();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
class UAgrarianSaveGame;
|
||||
class UAgrarianPersistentActorComponent;
|
||||
class AAgrarianGameCharacter;
|
||||
|
||||
UCLASS()
|
||||
class UAgrarianPersistenceSubsystem : public UGameInstanceSubsystem
|
||||
@@ -45,9 +46,17 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
|
||||
int32 RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors = true) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
|
||||
int32 CapturePlayers(UAgrarianSaveGame* SaveGame) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
|
||||
int32 RestorePlayers(const UAgrarianSaveGame* SaveGame) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
|
||||
bool SaveCurrentWorld() const;
|
||||
|
||||
protected:
|
||||
void FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const;
|
||||
void FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const;
|
||||
FString GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,9 @@ struct FAgrarianSavedPlayer
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
|
||||
FAgrarianSurvivalSnapshot Survival;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
|
||||
FAgrarianCareHistorySnapshot CareHistory;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Save")
|
||||
TArray<FAgrarianItemStack> Inventory;
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ void UAgrarianSurvivalComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
ClampSurvival();
|
||||
ClampCareHistory();
|
||||
BroadcastSurvivalChanged();
|
||||
}
|
||||
|
||||
@@ -32,6 +33,26 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
|
||||
Survival.Thirst -= ThirstDecayPerMinute * Minutes;
|
||||
Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;
|
||||
|
||||
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>())
|
||||
@@ -56,7 +77,13 @@ void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickT
|
||||
Survival.Health -= ColdDamagePerMinute * Minutes;
|
||||
}
|
||||
|
||||
if (Survival.SicknessSeverity >= 60.0f)
|
||||
{
|
||||
Survival.Health -= SicknessDamagePerMinute * (Survival.SicknessSeverity / 100.0f) * Minutes;
|
||||
}
|
||||
|
||||
ClampSurvival();
|
||||
ClampCareHistory();
|
||||
BroadcastSurvivalChanged();
|
||||
}
|
||||
|
||||
@@ -64,6 +91,7 @@ void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProp
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
DOREPLIFETIME(UAgrarianSurvivalComponent, Survival);
|
||||
DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);
|
||||
}
|
||||
|
||||
bool UAgrarianSurvivalComponent::IsAlive() const
|
||||
@@ -126,8 +154,45 @@ 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::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();
|
||||
}
|
||||
}
|
||||
@@ -136,7 +201,29 @@ void UAgrarianSurvivalComponent::SpendStamina(float Amount)
|
||||
{
|
||||
if (GetOwner() && GetOwner()->HasAuthority())
|
||||
{
|
||||
Survival.Stamina -= FMath::Max(0.0f, Amount);
|
||||
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();
|
||||
}
|
||||
@@ -147,14 +234,34 @@ 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()
|
||||
|
||||
@@ -27,6 +27,9 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Survival, Category = "Agrarian|Survival")
|
||||
FAgrarianSurvivalSnapshot Survival;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_CareHistory, Category = "Agrarian|Survival")
|
||||
FAgrarianCareHistorySnapshot CareHistory;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float HungerDecayPerMinute = 0.55f;
|
||||
|
||||
@@ -36,6 +39,15 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float StaminaRecoveryPerSecond = 14.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float ExhaustionGainPerLowStaminaSecond = 0.35f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float ExhaustionRecoveryPerSecond = 0.08f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0", ClampMax = "100"))
|
||||
float LowStaminaExhaustionThreshold = 20.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float StarvationDamagePerMinute = 3.0f;
|
||||
|
||||
@@ -45,6 +57,12 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float ColdDamagePerMinute = 4.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float SicknessDamagePerMinute = 1.5f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival|Rates", meta = (ClampMin = "0"))
|
||||
float SicknessRecoveryPerSecond = 0.02f;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
bool IsAlive() const;
|
||||
|
||||
@@ -66,13 +84,32 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void AddInjury(float Severity);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void AddSickness(float Severity);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void ReduceSickness(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void SpendStamina(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void AddExhaustion(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival")
|
||||
void ReduceExhaustion(float Amount);
|
||||
|
||||
protected:
|
||||
UFUNCTION()
|
||||
void OnRep_Survival();
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_CareHistory();
|
||||
|
||||
void ClampSurvival();
|
||||
void ClampCareHistory();
|
||||
void BroadcastSurvivalChanged();
|
||||
};
|
||||
|
||||
@@ -14,6 +14,112 @@ enum class EAgrarianWeatherType : uint8
|
||||
Storm UMETA(DisplayName = "Storm")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EAgrarianSeason : uint8
|
||||
{
|
||||
Winter UMETA(DisplayName = "Winter"),
|
||||
Spring UMETA(DisplayName = "Spring"),
|
||||
Summer UMETA(DisplayName = "Summer"),
|
||||
Autumn UMETA(DisplayName = "Autumn")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EAgrarianCropSeasonFit : uint8
|
||||
{
|
||||
Unknown UMETA(DisplayName = "Unknown"),
|
||||
FitsSeason UMETA(DisplayName = "Fits Season"),
|
||||
Marginal UMETA(DisplayName = "Marginal"),
|
||||
TooLong UMETA(DisplayName = "Too Long"),
|
||||
OutOfSeason UMETA(DisplayName = "Out Of Season")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAgrarianCalendarSnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
int32 DayOfYear = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
int32 Year = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
int32 AbsoluteDay = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
int32 DayOfSeason = 1;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
EAgrarianSeason Season = EAgrarianSeason::Winter;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
float HourOfDay = 8.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
bool bInsideGrowingSeason = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Calendar")
|
||||
int32 GrowingSeasonDaysRemaining = 0;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAgrarianGrowingSeasonProfile
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
FName TileId = NAME_None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
FString GrowingZoneLabel = TEXT("unknown");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "1", ClampMax = "366"))
|
||||
int32 GrowingSeasonStartDay = 80;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "1", ClampMax = "366"))
|
||||
int32 GrowingSeasonEndDay = 300;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "0", ClampMax = "366"))
|
||||
int32 FrostFreeDays = 220;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
float MinAverageGrowingTempC = 7.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season", meta = (ClampMin = "0", ClampMax = "90"))
|
||||
int32 CropSafetyBufferDays = 14;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
FString ClimateProfile = TEXT("unknown");
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAgrarianCropSeasonAssessment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
EAgrarianCropSeasonFit Fit = EAgrarianCropSeasonFit::Unknown;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
int32 DaysAvailable = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
int32 DaysRequired = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
int32 SafetyBufferDays = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
bool bCanPlantToday = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
FString GrowingZoneLabel = TEXT("unknown");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Growing Season")
|
||||
FText Reason;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAgrarianItemStack
|
||||
{
|
||||
@@ -125,6 +231,9 @@ struct FAgrarianSurvivalSnapshot
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||
float Stamina = 100.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||
float Exhaustion = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||
float Hunger = 100.0f;
|
||||
|
||||
@@ -136,4 +245,37 @@ struct FAgrarianSurvivalSnapshot
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||
float InjurySeverity = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Survival")
|
||||
float SicknessSeverity = 0.0f;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAgrarianCareHistorySnapshot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float NutritionQuality = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float IllnessBurden = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float InjuryBurden = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float SleepQuality = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float ShelterQuality = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float StressBurden = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float WorkloadBurden = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Care History")
|
||||
float TreatmentQuality = 1.0f;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user