This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
AgrarianGameArchive/Source/AgrarianGame/AgrarianDebugHUD.cpp
T
2026-05-17 17:57:17 -07:00

378 lines
13 KiB
C++

// Copyright Pacificao. All Rights Reserved.
#include "AgrarianDebugHUD.h"
#include "AgrarianCraftingComponent.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AgrarianSurvivalComponent.h"
#include "Engine/Canvas.h"
#include "GameFramework/CharacterMovementComponent.h"
void AAgrarianDebugHUD::DrawHUD()
{
Super::DrawHUD();
if (!Canvas)
{
return;
}
const AAgrarianGameCharacter* AgrarianCharacter = Cast<AAgrarianGameCharacter>(GetOwningPawn());
if (!AgrarianCharacter)
{
return;
}
DrawInteractionPrompt(AgrarianCharacter);
DrawCriticalStats(AgrarianCharacter->GetSurvivalComponent());
const float InventoryBottomY = DrawInventoryPanel(AgrarianCharacter);
DrawCraftingPanel(AgrarianCharacter, InventoryBottomY + 16.0f);
if (bShowDebugHUD)
{
float Y = 32.0f;
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);
}
}
void AAgrarianDebugHUD::DrawInteractionPrompt(const AAgrarianGameCharacter* AgrarianCharacter)
{
if (!bShowInteractionPrompt || !AgrarianCharacter || !AgrarianCharacter->HasInteractionPrompt() || !Canvas)
{
return;
}
const FString Prompt = FString::Printf(TEXT("[E] %s"), *AgrarianCharacter->GetInteractionPromptText().ToString());
float TextWidth = 0.0f;
float TextHeight = 0.0f;
GetTextSize(Prompt, TextWidth, TextHeight, nullptr, PromptTextScale);
const float X = (Canvas->ClipX - TextWidth) * 0.5f;
const float Y = Canvas->ClipY * 0.62f;
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("Shelter %3.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y, CriticalStatsTextScale, SurvivalComponent->CurrentWeatherProtection > 0.0f ? StableColor : WarningColor);
DrawScaledLine(FString::Printf(TEXT("Expose x%.2f %+3.1f C"), SurvivalComponent->CurrentWeatherExposureMultiplier, SurvivalComponent->CurrentWeatherTemperatureOffsetC), X, Y, CriticalStatsTextScale, SurvivalComponent->CurrentWeatherExposureMultiplier > 1.0f ? WarningColor : 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));
}
float AAgrarianDebugHUD::DrawInventoryPanel(const AAgrarianGameCharacter* AgrarianCharacter)
{
if (!bShowInventoryHUD || !AgrarianCharacter || !Canvas)
{
return 32.0f;
}
const UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter->GetInventoryComponent();
if (!InventoryComponent)
{
return 32.0f;
}
const float Scale = FMath::Max(0.25f, InventoryTextScale);
const float PanelWidth = 360.0f * Scale;
const float X = FMath::Max(32.0f, Canvas->ClipX - PanelWidth - 32.0f);
float Y = 32.0f;
const int32 VisibleRows = InventoryComponent->Items.IsEmpty()
? 1
: FMath::Min(InventoryComponent->Items.Num(), FMath::Max(1, MaxInventoryPanelRows));
const float LineHeight = 18.0f * Scale;
const float PanelHeight = (56.0f * Scale) + (VisibleRows * LineHeight);
DrawRect(FLinearColor(0.02f, 0.025f, 0.02f, 0.72f), X - (12.0f * Scale), Y - (10.0f * Scale), PanelWidth, PanelHeight);
DrawText(
FString::Printf(
TEXT("INVENTORY %d/%d slots %.1f wt"),
InventoryComponent->Items.Num(),
InventoryComponent->MaxSlots,
InventoryComponent->GetTotalWeight()),
FColor(160, 220, 140),
X,
Y,
nullptr,
Scale,
false);
Y += 24.0f * Scale;
if (InventoryComponent->Items.IsEmpty())
{
DrawText(TEXT("Empty"), FColor::Silver, X, Y, nullptr, Scale, false);
return (32.0f + PanelHeight);
}
for (int32 Index = 0; Index < VisibleRows; ++Index)
{
const FAgrarianItemStack& Stack = InventoryComponent->Items[Index];
const FText DisplayName = Stack.DisplayName.IsEmpty() ? FText::FromName(Stack.ItemId) : Stack.DisplayName;
FString ItemName = DisplayName.ToString();
if (ItemName.Len() > 24)
{
ItemName = ItemName.Left(21) + TEXT("...");
}
DrawText(
FString::Printf(TEXT("%02d %-24s x%-3d %5.1f"), Index + 1, *ItemName, Stack.Quantity, Stack.UnitWeight * Stack.Quantity),
FColor(225, 235, 220),
X,
Y,
nullptr,
Scale,
false);
Y += LineHeight;
}
return (32.0f + PanelHeight);
}
void AAgrarianDebugHUD::DrawCraftingPanel(const AAgrarianGameCharacter* AgrarianCharacter, float TopY)
{
if (!bShowCraftingHUD || !AgrarianCharacter || !Canvas)
{
return;
}
const UAgrarianCraftingComponent* CraftingComponent = AgrarianCharacter->GetCraftingComponent();
const UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter->GetInventoryComponent();
if (!CraftingComponent || !InventoryComponent)
{
return;
}
TArray<FAgrarianRecipe> Recipes;
CraftingComponent->GetKnownRecipes(Recipes);
const float Scale = FMath::Max(0.25f, InventoryTextScale);
const float PanelWidth = 420.0f * Scale;
const float X = FMath::Max(32.0f, Canvas->ClipX - PanelWidth - 32.0f);
float Y = FMath::Max(32.0f, TopY);
const int32 VisibleRows = Recipes.IsEmpty()
? 1
: FMath::Min(Recipes.Num(), FMath::Max(1, MaxCraftingPanelRows));
const float LineHeight = 18.0f * Scale;
const float PanelHeight = (56.0f * Scale) + (VisibleRows * LineHeight);
DrawRect(FLinearColor(0.02f, 0.025f, 0.02f, 0.72f), X - (12.0f * Scale), Y - (10.0f * Scale), PanelWidth, PanelHeight);
DrawText(
FString::Printf(TEXT("CRAFTING %d recipes"), Recipes.Num()),
FColor(160, 220, 140),
X,
Y,
nullptr,
Scale,
false);
Y += 24.0f * Scale;
if (Recipes.IsEmpty())
{
DrawText(TEXT("No known recipes"), FColor::Silver, X, Y, nullptr, Scale, false);
return;
}
for (int32 Index = 0; Index < VisibleRows; ++Index)
{
const FAgrarianRecipe& Recipe = Recipes[Index];
FText FailureReason;
const bool bCanCraft = CraftingComponent->CanCraft(Recipe.RecipeId, FailureReason);
FString RecipeName = Recipe.DisplayName.IsEmpty() ? Recipe.RecipeId.ToString() : Recipe.DisplayName.ToString();
if (RecipeName.Len() > 18)
{
RecipeName = RecipeName.Left(15) + TEXT("...");
}
FString IngredientSummary;
for (const FAgrarianItemStack& Ingredient : Recipe.Ingredients)
{
if (!IngredientSummary.IsEmpty())
{
IngredientSummary += TEXT(" ");
}
IngredientSummary += FString::Printf(
TEXT("%s %d/%d"),
*Ingredient.ItemId.ToString(),
InventoryComponent->GetItemCount(Ingredient.ItemId),
Ingredient.Quantity);
}
if (IngredientSummary.Len() > 28)
{
IngredientSummary = IngredientSummary.Left(25) + TEXT("...");
}
DrawText(
FString::Printf(TEXT("%02d %-18s %-28s"), Index + 1, *RecipeName, *IngredientSummary),
bCanCraft ? FColor(225, 235, 220) : FColor(170, 150, 125),
X,
Y,
nullptr,
Scale,
false);
Y += LineHeight;
}
}
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)
{
DrawLine(TEXT("Survival: unavailable"), X, Y, FColor::Red);
return;
}
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("Shelter: %.0f%%"), SurvivalComponent->CurrentWeatherProtection * 100.0f), X, Y);
DrawLine(FString::Printf(TEXT("Expose: x%.2f %+3.1f C"), SurvivalComponent->CurrentWeatherExposureMultiplier, SurvivalComponent->CurrentWeatherTemperatureOffsetC), 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;
}
void AAgrarianDebugHUD::DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y)
{
if (!InventoryComponent)
{
DrawLine(TEXT("Inventory: unavailable"), X, Y, FColor::Red);
return;
}
DrawLine(FString::Printf(TEXT("Inventory: %d/%d slots"), InventoryComponent->Items.Num(), InventoryComponent->MaxSlots), X, Y, FColor(160, 220, 140));
if (InventoryComponent->Items.IsEmpty())
{
DrawLine(TEXT("- empty"), X, Y, FColor::Silver);
return;
}
for (const FAgrarianItemStack& Stack : InventoryComponent->Items)
{
const FText DisplayName = Stack.DisplayName.IsEmpty() ? FText::FromName(Stack.ItemId) : Stack.DisplayName;
DrawLine(FString::Printf(TEXT("- %s x%d"), *DisplayName.ToString(), Stack.Quantity), X, Y);
}
}
void AAgrarianDebugHUD::DrawLine(const FString& Text, float X, float& Y, const FColor& Color)
{
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;
}