// Copyright Epic Games, Inc. All Rights Reserved. #include "AgrarianGamePlayerController.h" #include "AgrarianCampfire.h" #include "AgrarianCraftingComponent.h" #include "AgrarianDebugHUD.h" #include "AgrarianGameCharacter.h" #include "AgrarianInventoryComponent.h" #include "AgrarianItemPickup.h" #include "AgrarianMvpFrontendWidget.h" #include "AgrarianPersistenceSubsystem.h" #include "AgrarianShelterActor.h" #include "AgrarianSurvivalComponent.h" #include "Camera/CameraActor.h" #include "Components/SkeletalMeshComponent.h" #include "EnhancedInputSubsystems.h" #include "Engine/LocalPlayer.h" #include "Engine/SkeletalMesh.h" #include "GameFramework/Character.h" #include "GameFramework/CharacterMovementComponent.h" #include "InputCoreTypes.h" #include "InputMappingContext.h" #include "Materials/MaterialInterface.h" #include "Blueprint/UserWidget.h" #include "TimerManager.h" #include "AgrarianGame.h" #include "Widgets/Input/SVirtualJoystick.h" namespace { const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f); const TCHAR* GroundZeroServerTravelMapPath = TEXT("/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"); bool ResolveAgrarianServerTravelMap(const FName MapName, FString& OutTravelMap) { if (MapName == NAME_None || MapName == TEXT("GroundZero") || MapName == TEXT("L_GroundZeroTerrain_Test")) { OutTravelMap = GroundZeroServerTravelMapPath; return true; } return false; } bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary) { if (!SurvivalComponent || Quantity <= 0) { return false; } if (ItemId == TEXT("food")) { SurvivalComponent->AddFood(15.0f * Quantity); OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger"), 15.0f * Quantity); return true; } if (ItemId == TEXT("meat")) { SurvivalComponent->AddFood(22.0f * Quantity); SurvivalComponent->AddSickness(3.0f * Quantity); OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger but added raw-meat sickness risk"), 22.0f * Quantity); return true; } if (ItemId == TEXT("bandage")) { SurvivalComponent->ReduceInjury(18.0f * Quantity); SurvivalComponent->ReduceBleeding(28.0f * Quantity); SurvivalComponent->ReduceSprain(10.0f * Quantity); SurvivalComponent->RestoreHealth(4.0f * Quantity); OutEffectSummary = FString::Printf(TEXT("treated %.0f injury, %.0f bleeding, and %.0f sprain severity"), 18.0f * Quantity, 28.0f * Quantity, 10.0f * Quantity); return true; } return false; } const TCHAR* GetMvpCharacterProxyMeshPath(const FName ProxyId) { return ProxyId == TEXT("female") ? TEXT("/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple.SKM_Quinn_Simple") : TEXT("/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple.SKM_Manny_Simple"); } const TCHAR* GetMvpCharacterProxyMaterialPath(const FName ProxyId) { return ProxyId == TEXT("female") ? TEXT("/Game/Agrarian/Characters/Materials/M_AGR_CharacterProxy_Workwear_Female.M_AGR_CharacterProxy_Workwear_Female") : TEXT("/Game/Agrarian/Characters/Materials/M_AGR_CharacterProxy_Workwear_Male.M_AGR_CharacterProxy_Workwear_Male"); } } void AAgrarianGamePlayerController::BeginPlay() { Super::BeginPlay(); if (IsLocalPlayerController()) { if (MvpFrontendStartupDelaySeconds > 0.0f) { SetMvpFrontendPresentationActive(true); SetInputMode(FInputModeUIOnly()); bShowMouseCursor = false; GetWorldTimerManager().SetTimer( MvpFrontendStartupTimerHandle, this, &AAgrarianGamePlayerController::ShowMvpFrontend, MvpFrontendStartupDelaySeconds, false); } else { ShowMvpFrontend(); } } // only spawn touch controls on local player controllers if (ShouldUseTouchControls() && IsLocalPlayerController()) { // spawn the mobile controls widget MobileControlsWidget = CreateWidget(this, MobileControlsWidgetClass); if (MobileControlsWidget) { // add the controls to the player screen MobileControlsWidget->AddToPlayerScreen(0); } else { UE_LOG(LogAgrarianGame, Error, TEXT("Could not spawn mobile controls widget.")); } } } void AAgrarianGamePlayerController::AcknowledgePossession(APawn* P) { Super::AcknowledgePossession(P); if (!IsLocalPlayerController()) { return; } ApplyMvpCharacterProxyToPawn(); if (bMvpFrontendPresentationActive) { SetMvpFrontendPresentationActive(true); } } void AAgrarianGamePlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputComponent->BindKey(EKeys::Enter, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpConfirmInput); InputComponent->BindKey(EKeys::SpaceBar, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpConfirmInput); InputComponent->BindKey(EKeys::BackSpace, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpBackInput); InputComponent->BindKey(EKeys::Escape, IE_Pressed, this, &AAgrarianGamePlayerController::HandleMvpEscapeInput); // only add IMCs for local player controllers if (IsLocalPlayerController()) { // Add Input Mapping Contexts if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer())) { for (UInputMappingContext* CurrentContext : DefaultMappingContexts) { Subsystem->AddMappingContext(CurrentContext, 0); } // only add these IMCs if we're not using mobile touch input if (!ShouldUseTouchControls()) { for (UInputMappingContext* CurrentContext : MobileExcludedMappingContexts) { Subsystem->AddMappingContext(CurrentContext, 0); } } } } } bool AAgrarianGamePlayerController::ShouldUseTouchControls() const { // are we on a mobile platform? Should we force touch? return SVirtualJoystick::ShouldDisplayTouchInterface() || bForceTouchControls; } void AAgrarianGamePlayerController::ShowMvpFrontend() { if (!IsLocalPlayerController() || (MvpFrontendWidget && MvpFrontendWidget->IsInViewport())) { return; } if (!MvpFrontendWidgetClass) { MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass(); } if (!MvpFrontendWidget) { MvpFrontendWidget = CreateWidget(this, MvpFrontendWidgetClass); } if (!MvpFrontendWidget) { return; } MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); if (!MvpFrontendWidget->IsInViewport()) { MvpFrontendWidget->AddToPlayerScreen(10); } SetMvpFrontendPresentationActive(true); SetInputMode(FInputModeUIOnly().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock)); bShowMouseCursor = true; SetIgnoreMoveInput(true); SetIgnoreLookInput(true); } void AAgrarianGamePlayerController::ShowMvpPauseMenu() { if (!IsLocalPlayerController()) { return; } if (!MvpFrontendWidgetClass) { MvpFrontendWidgetClass = UAgrarianMvpFrontendWidget::StaticClass(); } if (!MvpFrontendWidget || !MvpFrontendWidget->IsInViewport()) { if (!MvpFrontendWidget) { MvpFrontendWidget = CreateWidget(this, MvpFrontendWidgetClass); } if (MvpFrontendWidget) { MvpFrontendWidget->AddToPlayerScreen(10); } } if (!MvpFrontendWidget) { return; } MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); SetMvpFrontendPresentationActive(true); SetInputMode(FInputModeUIOnly().SetWidgetToFocus(MvpFrontendWidget->TakeWidget()).SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock)); bShowMouseCursor = true; SetIgnoreMoveInput(true); SetIgnoreLookInput(true); } void AAgrarianGamePlayerController::HandleMvpConfirmInput() { if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport()) { MvpFrontendWidget->ConfirmActiveScreen(); return; } } void AAgrarianGamePlayerController::HandleMvpBackInput() { if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport()) { MvpFrontendWidget->BackFromActiveScreen(); } } void AAgrarianGamePlayerController::HandleMvpEscapeInput() { if (MvpFrontendWidget && MvpFrontendWidget->IsInViewport()) { MvpFrontendWidget->BackFromActiveScreen(); return; } ShowMvpPauseMenu(); } void AAgrarianGamePlayerController::SetMvpFrontendPresentationActive(bool bNewActive) { bMvpFrontendPresentationActive = bNewActive; SetIgnoreMoveInput(bNewActive); SetIgnoreLookInput(bNewActive); APawn* ControlledPawn = GetPawn(); if (ControlledPawn) { ControlledPawn->SetActorHiddenInGame(bNewActive); ControlledPawn->SetActorEnableCollision(!bNewActive); if (ACharacter* ControlledCharacter = Cast(ControlledPawn)) { if (UCharacterMovementComponent* MovementComponent = ControlledCharacter->GetCharacterMovement()) { if (bNewActive) { MovementComponent->DisableMovement(); } else { MovementComponent->SetMovementMode(MOVE_Walking); } } } } CacheAndApplyMvpHudSuppression(bNewActive); if (bNewActive) { CreateOrUpdateMvpFrontendCamera(); if (MvpFrontendCameraActor) { SetViewTarget(MvpFrontendCameraActor); } return; } if (ControlledPawn) { SetViewTarget(ControlledPawn); } } void AAgrarianGamePlayerController::CreateOrUpdateMvpFrontendCamera() { UWorld* World = GetWorld(); if (!World) { return; } const APawn* ControlledPawn = GetPawn(); const FVector TargetLocation = ControlledPawn ? ControlledPawn->GetActorLocation() + FVector(0.0f, 0.0f, 95.0f) : GroundZeroDeveloperTravelHomeLocation; const FVector CameraLocation = TargetLocation + FVector(-620.0f, -520.0f, 260.0f); const FRotator CameraRotation = (TargetLocation - CameraLocation).Rotation(); if (!MvpFrontendCameraActor) { FActorSpawnParameters SpawnParameters; SpawnParameters.Owner = this; SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; MvpFrontendCameraActor = World->SpawnActor(ACameraActor::StaticClass(), CameraLocation, CameraRotation, SpawnParameters); if (MvpFrontendCameraActor) { MvpFrontendCameraActor->SetActorHiddenInGame(true); } return; } MvpFrontendCameraActor->SetActorLocationAndRotation(CameraLocation, CameraRotation); } void AAgrarianGamePlayerController::CacheAndApplyMvpHudSuppression(bool bSuppress) { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { return; } if (bSuppress) { if (!bCachedMvpHudState) { bCachedShowDebugHUD = AgrarianHUD->bShowDebugHUD; bCachedShowMvpHudFrame = AgrarianHUD->bShowMvpHudFrame; bCachedShowCriticalStatsHUD = AgrarianHUD->bShowCriticalStatsHUD; bCachedShowInventoryHUD = AgrarianHUD->bShowInventoryHUD; bCachedShowCraftingHUD = AgrarianHUD->bShowCraftingHUD; bCachedShowInteractionPrompt = AgrarianHUD->bShowInteractionPrompt; bCachedShowDeathRespawnUI = AgrarianHUD->bShowDeathRespawnUI; bCachedShowDebugDevMenu = AgrarianHUD->bShowDebugDevMenu; bCachedMvpHudState = true; } AgrarianHUD->bShowDebugHUD = false; AgrarianHUD->bShowMvpHudFrame = false; AgrarianHUD->bShowCriticalStatsHUD = false; AgrarianHUD->bShowInventoryHUD = false; AgrarianHUD->bShowCraftingHUD = false; AgrarianHUD->bShowInteractionPrompt = false; AgrarianHUD->bShowDeathRespawnUI = false; AgrarianHUD->bShowDebugDevMenu = false; return; } if (bCachedMvpHudState) { AgrarianHUD->bShowDebugHUD = bCachedShowDebugHUD; AgrarianHUD->bShowMvpHudFrame = bCachedShowMvpHudFrame; AgrarianHUD->bShowCriticalStatsHUD = bCachedShowCriticalStatsHUD; AgrarianHUD->bShowInventoryHUD = bCachedShowInventoryHUD; AgrarianHUD->bShowCraftingHUD = bCachedShowCraftingHUD; AgrarianHUD->bShowInteractionPrompt = bCachedShowInteractionPrompt; AgrarianHUD->bShowDeathRespawnUI = bCachedShowDeathRespawnUI; AgrarianHUD->bShowDebugDevMenu = bCachedShowDebugDevMenu; bCachedMvpHudState = false; } } void AAgrarianGamePlayerController::ApplyMvpCharacterProxyToPawn() { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); USkeletalMeshComponent* MeshComponent = AgrarianCharacter ? AgrarianCharacter->GetMesh() : nullptr; if (!MeshComponent) { return; } if (USkeletalMesh* ProxyMesh = LoadObject(nullptr, GetMvpCharacterProxyMeshPath(SelectedMvpCharacterProxyId))) { MeshComponent->SetSkeletalMesh(ProxyMesh); } if (UMaterialInterface* ProxyMaterial = LoadObject(nullptr, GetMvpCharacterProxyMaterialPath(SelectedMvpCharacterProxyId))) { const int32 MaterialCount = FMath::Max(1, MeshComponent->GetNumMaterials()); for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex) { MeshComponent->SetMaterial(MaterialIndex, ProxyMaterial); } } // Blockout clothing and pack geometry was removed because it read as broken placeholder art in the investor build. } void AAgrarianGamePlayerController::AgrarianGrantItem(FName ItemId, int32 Quantity) { if (ItemId == NAME_None || Quantity <= 0) { ClientMessage(TEXT("Usage: AgrarianGrantItem ")); return; } ServerAgrarianGrantItem(ItemId, Quantity); } void AAgrarianGamePlayerController::AgrarianSaveWorld() { ServerAgrarianSaveWorld(); } void AAgrarianGamePlayerController::AgrarianLoadWorld() { ServerAgrarianLoadWorld(); } void AAgrarianGamePlayerController::AgrarianSurvival() { const AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); const UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr; if (!SurvivalComponent) { ClientMessage(TEXT("No Agrarian survival component found.")); return; } const FAgrarianSurvivalSnapshot& Survival = SurvivalComponent->Survival; ClientMessage(FString::Printf( TEXT("%s | Health %.1f | Stamina %.1f | Exhaustion %.1f | Hunger %.1f | Thirst %.1f | Temp %.1fC | Injury %.1f | Bleeding %.1f | Sprain %.1f | Sickness %.1f | Death %s"), Survival.bIsDead ? TEXT("DEAD") : TEXT("ALIVE"), Survival.Health, Survival.Stamina, Survival.Exhaustion, Survival.Hunger, Survival.Thirst, Survival.BodyTemperature, Survival.InjurySeverity, Survival.BleedingSeverity, Survival.SprainSeverity, Survival.SicknessSeverity, *Survival.LastDeathReason.ToString())); } void AAgrarianGamePlayerController::AgrarianHeal() { ServerAgrarianHeal(); } void AAgrarianGamePlayerController::AgrarianRespawn() { ServerAgrarianRespawn(); } void AAgrarianGamePlayerController::AgrarianDropItem(FName ItemId, int32 Quantity) { if (ItemId == NAME_None || Quantity <= 0) { ClientMessage(TEXT("Usage: AgrarianDropItem ")); return; } ServerAgrarianDropItem(ItemId, Quantity); } void AAgrarianGamePlayerController::AgrarianSplitStack(int32 StackIndex, int32 SplitQuantity) { if (StackIndex < 0 || SplitQuantity <= 0) { ClientMessage(TEXT("Usage: AgrarianSplitStack ")); return; } ServerAgrarianSplitStack(StackIndex, SplitQuantity); } void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity) { if (ItemId == NAME_None || Quantity <= 0) { ClientMessage(TEXT("Usage: AgrarianUseItem ")); return; } ServerAgrarianUseItem(ItemId, Quantity); } void AAgrarianGamePlayerController::AgrarianCraft(FName RecipeId) { if (RecipeId == NAME_None) { ClientMessage(TEXT("Usage: AgrarianCraft ")); return; } ServerAgrarianCraft(RecipeId); } void AAgrarianGamePlayerController::AgrarianCraftStatus() { const AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); const UAgrarianCraftingComponent* CraftingComponent = AgrarianCharacter ? AgrarianCharacter->GetCraftingComponent() : nullptr; if (!CraftingComponent) { ClientMessage(TEXT("No Agrarian crafting component found.")); return; } TArray Recipes; CraftingComponent->GetKnownRecipes(Recipes); if (Recipes.IsEmpty()) { ClientMessage(TEXT("No known Agrarian recipes.")); return; } ClientMessage(FString::Printf(TEXT("Known Agrarian recipes: %d"), Recipes.Num())); for (const FAgrarianRecipe& Recipe : Recipes) { FText FailureReason; const bool bCanCraft = CraftingComponent->CanCraft(Recipe.RecipeId, FailureReason); const FString RecipeName = Recipe.DisplayName.IsEmpty() ? Recipe.RecipeId.ToString() : Recipe.DisplayName.ToString(); ClientMessage(FString::Printf( TEXT("- %s (%s): %s"), *RecipeName, *Recipe.RecipeId.ToString(), bCanCraft ? TEXT("ready") : *FailureReason.ToString())); } } void AAgrarianGamePlayerController::AgrarianToggleInventoryUI() { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { ClientMessage(TEXT("No Agrarian HUD is active.")); return; } AgrarianHUD->bShowInventoryHUD = !AgrarianHUD->bShowInventoryHUD; ClientMessage(AgrarianHUD->bShowInventoryHUD ? TEXT("MVP inventory UI shown.") : TEXT("MVP inventory UI hidden.")); } void AAgrarianGamePlayerController::AgrarianToggleCraftingUI() { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { ClientMessage(TEXT("No Agrarian HUD is active.")); return; } AgrarianHUD->bShowCraftingHUD = !AgrarianHUD->bShowCraftingHUD; ClientMessage(AgrarianHUD->bShowCraftingHUD ? TEXT("MVP crafting UI shown.") : TEXT("MVP crafting UI hidden.")); } void AAgrarianGamePlayerController::AgrarianToggleInteractionPrompts() { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { ClientMessage(TEXT("No Agrarian HUD is active.")); return; } AgrarianHUD->bShowInteractionPrompt = !AgrarianHUD->bShowInteractionPrompt; ClientMessage(AgrarianHUD->bShowInteractionPrompt ? TEXT("MVP interaction prompts shown.") : TEXT("MVP interaction prompts hidden.")); } void AAgrarianGamePlayerController::AgrarianToggleDeathRespawnUI() { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { ClientMessage(TEXT("No Agrarian HUD is active.")); return; } AgrarianHUD->bShowDeathRespawnUI = !AgrarianHUD->bShowDeathRespawnUI; ClientMessage(AgrarianHUD->bShowDeathRespawnUI ? TEXT("MVP death/respawn UI shown.") : TEXT("MVP death/respawn UI hidden.")); } void AAgrarianGamePlayerController::AgrarianToggleDebugDevMenu() { AAgrarianDebugHUD* AgrarianHUD = GetHUD(); if (!AgrarianHUD) { ClientMessage(TEXT("No Agrarian HUD is active.")); return; } AgrarianHUD->bShowDebugDevMenu = !AgrarianHUD->bShowDebugDevMenu; ClientMessage(AgrarianHUD->bShowDebugDevMenu ? TEXT("MVP debug/dev menu shown.") : TEXT("MVP debug/dev menu hidden.")); } void AAgrarianGamePlayerController::AgrarianSetUiScale(float NewUiScale) { const float ClampedScale = FMath::Clamp(NewUiScale, 0.75f, 1.5f); if (MvpFrontendWidget) { MvpFrontendWidget->SetUiScale(ClampedScale); } if (AAgrarianDebugHUD* AgrarianHUD = GetHUD()) { AgrarianHUD->TextScale = ClampedScale; AgrarianHUD->CriticalStatsTextScale = ClampedScale; AgrarianHUD->InventoryTextScale = ClampedScale; AgrarianHUD->PromptTextScale = ClampedScale; } ClientMessage(FString::Printf(TEXT("MVP UI scale set to %.2f."), ClampedScale)); } void AAgrarianGamePlayerController::AgrarianToggleHighContrastUI() { if (!MvpFrontendWidget) { ClientMessage(TEXT("No MVP frontend widget is active.")); return; } MvpFrontendWidget->SetHighContrastMode(!MvpFrontendWidget->bUseHighContrast); ClientMessage(MvpFrontendWidget->bUseHighContrast ? TEXT("MVP high contrast UI enabled.") : TEXT("MVP high contrast UI disabled.")); } void AAgrarianGamePlayerController::AgrarianSelectCharacter(FName Archetype) { if (!MvpFrontendWidget) { ClientMessage(TEXT("No MVP frontend widget is active.")); return; } if (Archetype == TEXT("male") || Archetype == TEXT("YoungAdultMale")) { SelectedMvpCharacterProxyId = TEXT("male"); MvpFrontendWidget->SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); ApplyMvpCharacterProxyToPawn(); ClientMessage(TEXT("Selected MVP young adult male character archetype.")); return; } if (Archetype == TEXT("female") || Archetype == TEXT("YoungAdultFemale")) { SelectedMvpCharacterProxyId = TEXT("female"); MvpFrontendWidget->SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); ApplyMvpCharacterProxyToPawn(); ClientMessage(TEXT("Selected MVP young adult female character archetype.")); return; } ClientMessage(TEXT("Usage: AgrarianSelectCharacter male|female")); } void AAgrarianGamePlayerController::AgrarianCompleteFrontend() { ApplyMvpCharacterProxyToPawn(); SetMvpFrontendPresentationActive(false); SetInputMode(FInputModeGameOnly()); bShowMouseCursor = false; if (const APawn* ControlledPawn = GetPawn()) { SetControlRotation(FRotator(-8.0f, ControlledPawn->GetActorRotation().Yaw, 0.0f)); } } void AAgrarianGamePlayerController::AgrarianShowMvpScreen(FName ScreenName) { if (!MvpFrontendWidget) { ClientMessage(TEXT("No MVP frontend widget is active.")); return; } if (ScreenName == TEXT("main") || ScreenName == TEXT("MainMenu")) { MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); ClientMessage(TEXT("MVP frontend screen: main menu.")); return; } if (ScreenName == TEXT("character") || ScreenName == TEXT("CharacterSelection")) { MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); ClientMessage(TEXT("MVP frontend screen: character selection.")); return; } if (ScreenName == TEXT("join") || ScreenName == TEXT("JoinServer")) { MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer); ClientMessage(TEXT("MVP frontend screen: join server.")); return; } if (ScreenName == TEXT("loading") || ScreenName == TEXT("Loading")) { MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); ClientMessage(TEXT("MVP frontend screen: loading.")); return; } if (ScreenName == TEXT("saving") || ScreenName == TEXT("SavingAndQuit")) { MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit); ClientMessage(TEXT("MVP frontend screen: saving and quit.")); return; } ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join|loading|saving")); } void AAgrarianGamePlayerController::AgrarianInvestorSmokeTest(float CaptureDelaySeconds, float QuitDelaySeconds) { if (!IsLocalPlayerController()) { return; } const float ClampedCaptureDelaySeconds = FMath::Max(2.0f, CaptureDelaySeconds); const float ClampedQuitDelaySeconds = FMath::Max(ClampedCaptureDelaySeconds + 4.0f, QuitDelaySeconds); FTimerDelegate EnterWorldDelegate; EnterWorldDelegate.BindWeakLambda(this, [this]() { if (!MvpFrontendWidget) { ShowMvpFrontend(); } SelectedMvpCharacterProxyId = TEXT("female"); if (MvpFrontendWidget) { MvpFrontendWidget->SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); } AgrarianCompleteFrontend(); if (AAgrarianDebugHUD* AgrarianHUD = GetHUD()) { AgrarianHUD->bShowDebugHUD = false; AgrarianHUD->bShowCriticalStatsHUD = false; AgrarianHUD->bShowInventoryHUD = false; AgrarianHUD->bShowCraftingHUD = false; } if (APawn* ControlledPawn = GetPawn()) { const FRotator InvestorViewRotation(-4.0f, 42.0f, 0.0f); ControlledPawn->SetActorRotation(FRotator(0.0f, InvestorViewRotation.Yaw, 0.0f)); SetControlRotation(InvestorViewRotation); } ClientMessage(TEXT("Investor smoke test entered Ground Zero as the female MVP character proxy.")); }); FTimerHandle EnterWorldTimerHandle; GetWorldTimerManager().SetTimer(EnterWorldTimerHandle, EnterWorldDelegate, 0.75f, false); FTimerDelegate ScreenshotDelegate; ScreenshotDelegate.BindWeakLambda(this, [this]() { ClientMessage(TEXT("Investor smoke test capturing gameplay screenshot.")); ConsoleCommand(TEXT("HighResShot 1")); }); FTimerHandle ScreenshotTimerHandle; GetWorldTimerManager().SetTimer(ScreenshotTimerHandle, ScreenshotDelegate, ClampedCaptureDelaySeconds, false); FTimerDelegate QuitDelegate; QuitDelegate.BindWeakLambda(this, [this]() { ClientMessage(TEXT("Investor smoke test completed; quitting packaged client.")); ConsoleCommand(TEXT("quit")); }); FTimerHandle QuitTimerHandle; GetWorldTimerManager().SetTimer(QuitTimerHandle, QuitDelegate, ClampedQuitDelaySeconds, false); ClientMessage(FString::Printf( TEXT("Investor smoke test scheduled: screenshot in %.1fs, quit in %.1fs."), ClampedCaptureDelaySeconds, ClampedQuitDelaySeconds)); } void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z) { ServerAgrarianTravel(FVector(X, Y, Z)); } void AAgrarianGamePlayerController::AgrarianTravelHome() { ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation); } void AAgrarianGamePlayerController::AgrarianServerTravel(FName MapName) { ServerAgrarianServerTravel(MapName); } void AAgrarianGamePlayerController::ServerAgrarianGrantItem_Implementation(FName ItemId, int32 Quantity) { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr; if (!InventoryComponent) { ClientMessage(TEXT("No Agrarian inventory component found.")); return; } FAgrarianItemStack Stack; Stack.ItemId = ItemId; Stack.DisplayName = FText::FromName(ItemId); Stack.Quantity = Quantity; if (InventoryComponent->AddItem(Stack)) { ClientMessage(FString::Printf(TEXT("Granted %d x %s."), Quantity, *ItemId.ToString())); } else { ClientMessage(FString::Printf(TEXT("Failed to grant %d x %s."), Quantity, *ItemId.ToString())); } } void AAgrarianGamePlayerController::ServerAgrarianSaveWorld_Implementation() { UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem() : nullptr; if (!Persistence) { ClientMessage(TEXT("No Agrarian persistence subsystem found.")); return; } const bool bSaved = Persistence->SaveCurrentWorld(); ClientMessage(bSaved ? TEXT("Agrarian world saved.") : TEXT("Agrarian world save failed.")); } void AAgrarianGamePlayerController::ServerAgrarianLoadWorld_Implementation() { UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem() : nullptr; if (!Persistence) { ClientMessage(TEXT("No Agrarian persistence subsystem found.")); return; } Persistence->RegisterWorldActorClass(TEXT("primitive_shelter"), AAgrarianShelterActor::StaticClass()); Persistence->RegisterWorldActorClass(TEXT("campfire"), AAgrarianCampfire::StaticClass()); int32 RestoredPlayerCount = 0; int32 RestoredActorCount = 0; const bool bLoaded = Persistence->LoadCurrentWorld(RestoredPlayerCount, RestoredActorCount); ClientMessage(FString::Printf( TEXT("%s Restored players: %d. Restored actors: %d."), bLoaded ? TEXT("Agrarian world loaded.") : TEXT("Agrarian world load restored actors/players, but world state restore failed."), RestoredPlayerCount, RestoredActorCount)); } void AAgrarianGamePlayerController::ServerAgrarianHeal_Implementation() { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr; if (!SurvivalComponent) { ClientMessage(TEXT("No Agrarian survival component found.")); return; } SurvivalComponent->Revive(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.")); } void AAgrarianGamePlayerController::ServerAgrarianRespawn_Implementation() { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr; if (!AgrarianCharacter || !SurvivalComponent) { ClientMessage(TEXT("No Agrarian character/survival component found.")); return; } if (!SurvivalComponent->IsDead()) { ClientMessage(TEXT("AgrarianRespawn is only available after death. Use AgrarianHeal for admin recovery.")); return; } if (UCharacterMovementComponent* MovementComponent = AgrarianCharacter->GetCharacterMovement()) { MovementComponent->StopMovementImmediately(); } AgrarianCharacter->SetActorLocation(GroundZeroDeveloperTravelHomeLocation, false, nullptr, ETeleportType::TeleportPhysics); SurvivalComponent->Revive(60.0f); SurvivalComponent->AddFood(35.0f); SurvivalComponent->AddWater(35.0f); SurvivalComponent->ReduceExhaustion(100.0f); SurvivalComponent->ReduceInjury(100.0f); SurvivalComponent->ReduceBleeding(100.0f); SurvivalComponent->ReduceSprain(100.0f); SurvivalComponent->ReduceSickness(100.0f); SurvivalComponent->AddWarmth(37.0f - SurvivalComponent->Survival.BodyTemperature); ClientMessage(TEXT("Agrarian MVP respawn complete: returned to Ground Zero with acute conditions stabilized.")); } void AAgrarianGamePlayerController::ServerAgrarianDropItem_Implementation(FName ItemId, int32 Quantity) { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr; if (!AgrarianCharacter || !InventoryComponent) { ClientMessage(TEXT("No Agrarian inventory component found.")); return; } FAgrarianItemStack DroppedStack; if (!InventoryComponent->ExtractItem(ItemId, Quantity, DroppedStack)) { ClientMessage(FString::Printf(TEXT("Could not drop %d x %s."), Quantity, *ItemId.ToString())); return; } const FVector DropLocation = AgrarianCharacter->GetActorLocation() + AgrarianCharacter->GetActorForwardVector() * 150.0f + FVector(0.0f, 0.0f, 40.0f); const FRotator DropRotation(0.0f, AgrarianCharacter->GetActorRotation().Yaw, 0.0f); FActorSpawnParameters SpawnParameters; SpawnParameters.Owner = AgrarianCharacter; SpawnParameters.Instigator = AgrarianCharacter; SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AAgrarianItemPickup* Pickup = GetWorld() ? GetWorld()->SpawnActor(AAgrarianItemPickup::StaticClass(), DropLocation, DropRotation, SpawnParameters) : nullptr; if (!Pickup) { InventoryComponent->AddItem(DroppedStack); ClientMessage(FString::Printf(TEXT("Failed to spawn dropped %s; item restored."), *ItemId.ToString())); return; } Pickup->PickupStack = DroppedStack; Pickup->Quantity = DroppedStack.Quantity; ClientMessage(FString::Printf(TEXT("Dropped %d x %s."), DroppedStack.Quantity, *ItemId.ToString())); } void AAgrarianGamePlayerController::ServerAgrarianSplitStack_Implementation(int32 StackIndex, int32 SplitQuantity) { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr; if (!InventoryComponent) { ClientMessage(TEXT("No Agrarian inventory component found.")); return; } if (InventoryComponent->SplitStackByIndex(StackIndex, SplitQuantity)) { ClientMessage(FString::Printf(TEXT("Split %d items from stack %d."), SplitQuantity, StackIndex)); } else { ClientMessage(FString::Printf(TEXT("Could not split %d items from stack %d."), SplitQuantity, StackIndex)); } } void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation(FName ItemId, int32 Quantity) { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr; UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr; if (!InventoryComponent || !SurvivalComponent) { ClientMessage(TEXT("No Agrarian inventory or survival component found.")); return; } FAgrarianItemStack UsedStack; if (!InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack)) { ClientMessage(FString::Printf(TEXT("Could not use %d x %s."), Quantity, *ItemId.ToString())); return; } FString EffectSummary; if (!ApplyAgrarianItemUseEffect(ItemId, UsedStack.Quantity, SurvivalComponent, EffectSummary)) { InventoryComponent->AddItem(UsedStack); ClientMessage(FString::Printf(TEXT("%s cannot be used yet; item restored."), *ItemId.ToString())); return; } ClientMessage(FString::Printf(TEXT("Used %d x %s: %s."), UsedStack.Quantity, *ItemId.ToString(), *EffectSummary)); } void AAgrarianGamePlayerController::ServerAgrarianCraft_Implementation(FName RecipeId) { AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); UAgrarianCraftingComponent* CraftingComponent = AgrarianCharacter ? AgrarianCharacter->GetCraftingComponent() : nullptr; if (!CraftingComponent) { ClientMessage(TEXT("No Agrarian crafting component found.")); return; } FText FailureReason; if (!CraftingComponent->CanCraft(RecipeId, FailureReason)) { ClientMessage(FString::Printf(TEXT("Cannot craft %s: %s"), *RecipeId.ToString(), *FailureReason.ToString())); return; } if (CraftingComponent->Craft(RecipeId)) { ClientMessage(FString::Printf(TEXT("Crafted %s."), *RecipeId.ToString())); } else { ClientMessage(FString::Printf(TEXT("Failed to craft %s."), *RecipeId.ToString())); } } void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination) { APawn* ControlledPawn = GetPawn(); if (!ControlledPawn) { ClientMessage(TEXT("No controlled pawn found for developer travel.")); return; } if (!Destination.ContainsNaN()) { ControlledPawn->TeleportTo(Destination, ControlledPawn->GetActorRotation(), false, true); if (ACharacter* ControlledCharacter = Cast(ControlledPawn)) { if (UCharacterMovementComponent* Movement = ControlledCharacter->GetCharacterMovement()) { Movement->StopMovementImmediately(); } } ClientMessage(FString::Printf( TEXT("Developer travel complete: X %.1f Y %.1f Z %.1f"), Destination.X, Destination.Y, Destination.Z)); } else { ClientMessage(TEXT("Developer travel failed: invalid destination.")); } } void AAgrarianGamePlayerController::ServerAgrarianServerTravel_Implementation(FName MapName) { UWorld* World = GetWorld(); if (!World || !HasAuthority()) { ClientMessage(TEXT("Agrarian server travel failed: no authoritative world.")); return; } FString TravelMap; if (!ResolveAgrarianServerTravelMap(MapName, TravelMap)) { ClientMessage(TEXT("Usage: AgrarianServerTravel GroundZero")); return; } const FString TravelURL = FString::Printf(TEXT("%s?listen"), *TravelMap); ClientMessage(FString::Printf(TEXT("Agrarian server travel requested: %s"), *TravelURL)); World->ServerTravel(TravelURL, false, false); }