// Copyright Epic Games, Inc. All Rights Reserved. #include "AgrarianGamePlayerController.h" #include "AgrarianCampfire.h" #include "AgrarianCraftingComponent.h" #include "AgrarianGameCharacter.h" #include "AgrarianInventoryComponent.h" #include "AgrarianItemPickup.h" #include "AgrarianPersistenceSubsystem.h" #include "AgrarianShelterActor.h" #include "AgrarianSurvivalComponent.h" #include "EnhancedInputSubsystems.h" #include "Engine/LocalPlayer.h" #include "GameFramework/Character.h" #include "GameFramework/CharacterMovementComponent.h" #include "InputMappingContext.h" #include "Blueprint/UserWidget.h" #include "AgrarianGame.h" #include "Widgets/Input/SVirtualJoystick.h" namespace { const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f); 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->RestoreHealth(4.0f * Quantity); OutEffectSummary = FString::Printf(TEXT("treated %.0f injury severity"), 18.0f * Quantity); return true; } return false; } } void AAgrarianGamePlayerController::BeginPlay() { Super::BeginPlay(); // 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::SetupInputComponent() { Super::SetupInputComponent(); // 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::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("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.SicknessSeverity)); } void AAgrarianGamePlayerController::AgrarianHeal() { ServerAgrarianHeal(); } 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::AgrarianTravel(float X, float Y, float Z) { ServerAgrarianTravel(FVector(X, Y, Z)); } void AAgrarianGamePlayerController::AgrarianTravelHome() { ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation); } 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->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.")); } 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.")); } }