// Copyright Pacificao. All Rights Reserved. #include "AgrarianMvpFrontendWidget.h" #include "AgrarianGamePlayerController.h" #include "Blueprint/WidgetTree.h" #include "Components/Border.h" #include "Components/Button.h" #include "Components/ButtonSlot.h" #include "Components/HorizontalBox.h" #include "Components/HorizontalBoxSlot.h" #include "Components/SizeBox.h" #include "Components/TextBlock.h" #include "Components/VerticalBox.h" #include "Components/VerticalBoxSlot.h" #include "GameFramework/PlayerController.h" #include "InputCoreTypes.h" #include "Styling/CoreStyle.h" #include "TimerManager.h" void UAgrarianMvpFrontendWidget::NativeConstruct() { Super::NativeConstruct(); SetIsFocusable(true); RebuildFrontendTree(); FocusPrimaryButton(); } FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) { if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Left || Key == EKeys::A) { SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); return FReply::Handled(); } if (Key == EKeys::Right || Key == EKeys::D) { SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); return FReply::Handled(); } if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { ConfirmActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::BackSpace || Key == EKeys::Escape) { BackFromActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar) { ConfirmActiveScreen(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Enter || Key == EKeys::SpaceBar || Key == EKeys::Escape) { ConfirmActiveScreen(); return FReply::Handled(); } if (Key == EKeys::Q) { SaveAndQuit(); return FReply::Handled(); } if (Key == EKeys::S) { SaveGame(); return FReply::Handled(); } if (Key == EKeys::O) { SetActiveScreen(EAgrarianMvpFrontendScreen::Settings); return FReply::Handled(); } if (Key == EKeys::X) { QuitWithoutSaving(); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings || ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) { const FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Escape || Key == EKeys::BackSpace || Key == EKeys::Enter || Key == EKeys::SpaceBar) { SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); return FReply::Handled(); } } else if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { return FReply::Handled(); } return Super::NativeOnKeyDown(InGeometry, InKeyEvent); } void UAgrarianMvpFrontendWidget::SetActiveScreen(EAgrarianMvpFrontendScreen NewScreen) { ActiveScreen = NewScreen; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype NewArchetype) { SelectedCharacterArchetype = NewArchetype; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetUiScale(float NewUiScale) { UiScale = FMath::Clamp(NewUiScale, 0.75f, 1.5f); RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::SetHighContrastMode(bool bNewUseHighContrast) { bUseHighContrast = bNewUseHighContrast; RebuildFrontendTree(); FocusPrimaryButton(); } void UAgrarianMvpFrontendWidget::ConfirmActiveScreen() { ContinueFromActiveScreen(); } void UAgrarianMvpFrontendWidget::BackFromActiveScreen() { ReturnFromActiveScreen(); } void UAgrarianMvpFrontendWidget::SaveAndQuit() { SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit); if (UWorld* World = GetWorld()) { FTimerHandle SaveAndQuitTimerHandle; World->GetTimerManager().SetTimer(SaveAndQuitTimerHandle, this, &UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit, 0.35f, false); return; } ExecuteSaveAndQuit(); } void UAgrarianMvpFrontendWidget::SaveGame() { ExecuteSaveGame(); SetActiveScreen(EAgrarianMvpFrontendScreen::GameSaved); } void UAgrarianMvpFrontendWidget::QuitWithoutSaving() { ExecuteQuitWithoutSaving(); } void UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit() { if (APlayerController* PlayerController = GetOwningPlayer()) { PlayerController->ConsoleCommand(TEXT("AgrarianSaveWorld")); PlayerController->ConsoleCommand(TEXT("quit")); } } void UAgrarianMvpFrontendWidget::ExecuteSaveGame() { if (APlayerController* PlayerController = GetOwningPlayer()) { PlayerController->ConsoleCommand(TEXT("AgrarianSaveWorld")); } } void UAgrarianMvpFrontendWidget::ExecuteQuitWithoutSaving() { if (APlayerController* PlayerController = GetOwningPlayer()) { PlayerController->ConsoleCommand(TEXT("quit")); } } void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen() { if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { CompleteFrontendFlow(); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { CompleteFrontendFlow(); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings || ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) { SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { CompleteFrontendFlow(); return; } } void UAgrarianMvpFrontendWidget::ReturnFromActiveScreen() { if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { CompleteFrontendFlow(); } if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings || ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) { SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); } } void UAgrarianMvpFrontendWidget::CompleteFrontendFlow() { if (APlayerController* PlayerController = GetOwningPlayer()) { if (AAgrarianGamePlayerController* AgrarianPlayerController = Cast(PlayerController)) { if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection || ActiveScreen == EAgrarianMvpFrontendScreen::Loading) { AgrarianPlayerController->AgrarianSelectCharacter(SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale ? TEXT("female") : TEXT("male")); } AgrarianPlayerController->AgrarianCompleteFrontend(); } else { PlayerController->SetInputMode(FInputModeGameOnly()); PlayerController->bShowMouseCursor = false; PlayerController->ResetIgnoreMoveInput(); PlayerController->ResetIgnoreLookInput(); } } RemoveFromParent(); } void UAgrarianMvpFrontendWidget::RebuildFrontendTree() { if (!WidgetTree) { return; } PrimaryFocusButton = nullptr; const float Scale = FMath::Clamp(UiScale, 0.75f, 1.5f); const FLinearColor BackdropColor = bUseHighContrast ? FLinearColor::Black : FLinearColor(0.015f, 0.018f, 0.014f, 0.99f); const FLinearColor PanelColor = bUseHighContrast ? FLinearColor(0.0f, 0.0f, 0.0f, 0.98f) : FLinearColor(0.035f, 0.045f, 0.034f, 0.96f); const FLinearColor AccentColor = bUseHighContrast ? FLinearColor(0.95f, 0.95f, 0.30f, 1.0f) : FLinearColor(0.45f, 0.72f, 0.40f, 1.0f); const FLinearColor TextColor = bUseHighContrast ? FLinearColor::White : FLinearColor(0.92f, 0.98f, 0.84f, 1.0f); const FLinearColor MutedTextColor = bUseHighContrast ? FLinearColor(0.86f, 0.86f, 0.66f, 1.0f) : FLinearColor(0.72f, 0.80f, 0.68f, 1.0f); const FLinearColor ButtonColor = bUseHighContrast ? FLinearColor(0.30f, 0.30f, 0.02f, 1.0f) : FLinearColor(0.22f, 0.40f, 0.18f, 0.98f); const FLinearColor ButtonHoverColor = bUseHighContrast ? FLinearColor(0.48f, 0.48f, 0.08f, 1.0f) : FLinearColor(0.35f, 0.58f, 0.30f, 1.0f); const FLinearColor SecondaryButtonColor = bUseHighContrast ? FLinearColor(0.12f, 0.12f, 0.12f, 1.0f) : FLinearColor(0.10f, 0.13f, 0.09f, 0.98f); const FLinearColor QuitButtonColor = bUseHighContrast ? FLinearColor(0.32f, 0.10f, 0.08f, 1.0f) : FLinearColor(0.42f, 0.22f, 0.18f, 0.98f); UBorder* RootBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("MvpFrontendRoot")); RootBorder->SetBrushColor(BackdropColor); RootBorder->SetPadding(FMargin(24.0f * Scale)); RootBorder->SetHorizontalAlignment(HAlign_Center); RootBorder->SetVerticalAlignment(VAlign_Center); WidgetTree->RootWidget = RootBorder; USizeBox* PanelSizeBox = WidgetTree->ConstructWidget(USizeBox::StaticClass(), TEXT("MvpFrontendPanelSize")); PanelSizeBox->SetWidthOverride(780.0f * Scale); PanelSizeBox->SetMinDesiredWidth(420.0f); PanelSizeBox->SetMinDesiredHeight(300.0f); RootBorder->SetContent(PanelSizeBox); UBorder* PanelBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("MvpFrontendPanel")); PanelBorder->SetBrushColor(PanelColor); PanelBorder->SetPadding(FMargin(42.0f * Scale, 34.0f * Scale)); PanelSizeBox->SetContent(PanelBorder); UVerticalBox* Panel = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("MvpFrontendStack")); PanelBorder->SetContent(Panel); if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { AddText(Panel, FText::FromString(TEXT("Pause Menu")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, MainMenuTitle, FMath::RoundToInt(54.0f * Scale), true, TextColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Gameplay is paused while this menu is active.")), FMath::RoundToInt(22.0f * Scale), false, MutedTextColor, 34.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Resume")), ButtonColor, ButtonHoverColor, 16.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); UButton* SaveButton = AddButton(Panel, FText::FromString(TEXT("Save Game")), SecondaryButtonColor, ButtonHoverColor, 12.0f * Scale); SaveButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleSaveGameClicked); UButton* SettingsButton = AddButton(Panel, FText::FromString(TEXT("Settings")), SecondaryButtonColor, ButtonHoverColor, 12.0f * Scale); SettingsButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleSettingsClicked); UButton* QuitButton = AddButton(Panel, FText::FromString(TEXT("Save & Exit")), QuitButtonColor, FLinearColor(0.58f, 0.28f, 0.22f, 1.0f), 12.0f * Scale); QuitButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked); UButton* QuitWithoutSavingButton = AddButton(Panel, FText::FromString(TEXT("Quit Without Saving")), SecondaryButtonColor, FLinearColor(0.46f, 0.20f, 0.16f, 1.0f), 28.0f * Scale); QuitWithoutSavingButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleQuitWithoutSavingClicked); AddText(Panel, FText::FromString(TEXT("Esc resumes. S saves. O opens settings. Q saves and exits. X exits without saving.")), FMath::RoundToInt(16.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::CharacterSelection) { AddText(Panel, FText::FromString(TEXT("Character Selection")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Choose your first pioneer")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Select the person who will step into Ground Zero.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 24.0f * Scale); UHorizontalBox* CharacterRow = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass(), TEXT("CharacterRow")); if (UVerticalBoxSlot* RowSlot = Panel->AddChildToVerticalBox(CharacterRow)) { RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 22.0f * Scale)); } const bool bMaleSelected = SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultMale; const bool bFemaleSelected = SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale; UButton* MaleButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("MalePioneerButton")); MaleButton->SetBackgroundColor(bMaleSelected ? ButtonHoverColor : SecondaryButtonColor); MaleButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked); UVerticalBox* MaleStack = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("MalePioneerStack")); MaleButton->SetContent(MaleStack); AddText(MaleStack, FText::FromString(TEXT("Young adult male")), FMath::RoundToInt(21.0f * Scale), true, TextColor, 8.0f * Scale); AddText(MaleStack, FText::FromString(TEXT("Average build, practical workwear proxy, survival baseline.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 18.0f * Scale); AddText(MaleStack, bMaleSelected ? FText::FromString(TEXT("Selected")) : FText::FromString(TEXT("Available")), FMath::RoundToInt(15.0f * Scale), true, bMaleSelected ? AccentColor : MutedTextColor, 0.0f); if (UHorizontalBoxSlot* MaleSlot = CharacterRow->AddChildToHorizontalBox(MaleButton)) { MaleSlot->SetPadding(FMargin(0.0f, 0.0f, 12.0f * Scale, 0.0f)); MaleSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } UButton* FemaleButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("FemalePioneerButton")); FemaleButton->SetBackgroundColor(bFemaleSelected ? ButtonHoverColor : SecondaryButtonColor); FemaleButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked); UVerticalBox* FemaleStack = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("FemalePioneerStack")); FemaleButton->SetContent(FemaleStack); AddText(FemaleStack, FText::FromString(TEXT("Young adult female")), FMath::RoundToInt(21.0f * Scale), true, TextColor, 8.0f * Scale); AddText(FemaleStack, FText::FromString(TEXT("Average build, practical workwear proxy, survival baseline.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 18.0f * Scale); AddText(FemaleStack, bFemaleSelected ? FText::FromString(TEXT("Selected")) : FText::FromString(TEXT("Available")), FMath::RoundToInt(15.0f * Scale), true, bFemaleSelected ? AccentColor : MutedTextColor, 0.0f); if (UHorizontalBoxSlot* FemaleSlot = CharacterRow->AddChildToHorizontalBox(FemaleButton)) { FemaleSlot->SetPadding(FMargin(12.0f * Scale, 0.0f, 0.0f, 0.0f)); FemaleSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Enter Ground Zero")), ButtonColor, ButtonHoverColor, 10.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); AddText(Panel, FText::Format(FText::FromString(TEXT("Selected {0}: {1}. Click a card or use Left/Right, then continue.")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { AddText(Panel, FText::FromString(TEXT("Server Join")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Join MVP server")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel()), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 36.0f * Scale); AddText(Panel, FText::FromString(TEXT("Server address")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 4.0f * Scale); AddText(Panel, JoinServerAddress, FMath::RoundToInt(24.0f * Scale), true, TextColor, 42.0f * Scale); UHorizontalBox* ButtonRow = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass(), TEXT("JoinButtonRow")); if (UVerticalBoxSlot* RowSlot = Panel->AddChildToVerticalBox(ButtonRow)) { RowSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 18.0f * Scale)); } PrimaryFocusButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("ContinueToLoadingButton")); PrimaryFocusButton->SetBackgroundColor(ButtonColor); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); UTextBlock* ContinueLabel = AddText(nullptr, FText::FromString(TEXT("Continue to loading")), FMath::RoundToInt(20.0f * Scale), true, TextColor, 0.0f); PrimaryFocusButton->SetContent(ContinueLabel); if (UButtonSlot* ContinueLabelSlot = Cast(ContinueLabel->Slot)) { ContinueLabelSlot->SetPadding(FMargin(22.0f, 12.0f)); ContinueLabelSlot->SetHorizontalAlignment(HAlign_Center); } if (UHorizontalBoxSlot* ContinueSlot = ButtonRow->AddChildToHorizontalBox(PrimaryFocusButton)) { ContinueSlot->SetPadding(FMargin(0.0f, 0.0f, 12.0f * Scale, 0.0f)); ContinueSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } UButton* BackButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("BackButton")); BackButton->SetBackgroundColor(SecondaryButtonColor); BackButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleBackClicked); UTextBlock* BackLabel = AddText(nullptr, FText::FromString(TEXT("Back")), FMath::RoundToInt(20.0f * Scale), true, TextColor, 0.0f); BackButton->SetContent(BackLabel); if (UButtonSlot* BackLabelSlot = Cast(BackLabel->Slot)) { BackLabelSlot->SetPadding(FMargin(22.0f, 12.0f)); BackLabelSlot->SetHorizontalAlignment(HAlign_Center); } if (UHorizontalBoxSlot* BackSlot = ButtonRow->AddChildToHorizontalBox(BackButton)) { BackSlot->SetPadding(FMargin(12.0f * Scale, 0.0f, 0.0f, 0.0f)); BackSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); } AddText(Panel, FText::FromString(TEXT("Click continue, press Enter, or press Backspace/Escape to revise your pioneer.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::SavingAndQuit) { AddText(Panel, FText::FromString(TEXT("Saving World")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Writing the current world state")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("The demo will close after the save command is issued.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 0.0f); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) { AddText(Panel, FText::FromString(TEXT("Game Saved")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("World state saved")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("Return to the pause menu before resuming or exiting.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 28.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Back to Pause Menu")), ButtonColor, ButtonHoverColor, 0.0f); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleBackClicked); return; } if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings) { AddText(Panel, FText::FromString(TEXT("Settings")), FMath::RoundToInt(20.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Player Options")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("Interface scale and high contrast are available now through tester commands. Units, controls, hardware, audio, and accessibility options are queued for the settings roadmap.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 28.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Back to Pause Menu")), ButtonColor, ButtonHoverColor, 0.0f); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandleBackClicked); return; } AddText(Panel, FText::FromString(TEXT("Loading Segment")), FMath::RoundToInt(18.0f * Scale), true, AccentColor, 6.0f * Scale); AddText(Panel, FText::FromString(TEXT("Preparing Ground Zero")), FMath::RoundToInt(34.0f * Scale), true, TextColor, 8.0f * Scale); AddText(Panel, FText::FromString(TEXT("Loading terrain, weather, survival state, and server session data.")), FMath::RoundToInt(18.0f * Scale), false, MutedTextColor, 70.0f * Scale); AddText(Panel, FText::Format(FText::FromString(TEXT("{0}: {1} | Server: {2}")), GetSelectedRoleLabel(), GetSelectedCharacterLabel(), JoinServerAddress), FMath::RoundToInt(18.0f * Scale), false, TextColor, 34.0f * Scale); PrimaryFocusButton = AddButton(Panel, FText::FromString(TEXT("Enter Ground Zero")), ButtonColor, ButtonHoverColor, 14.0f * Scale); PrimaryFocusButton->OnClicked.AddDynamic(this, &UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked); AddText(Panel, FText::FromString(TEXT("Click or press Enter to close the MVP menu and begin testing.")), FMath::RoundToInt(15.0f * Scale), false, MutedTextColor, 0.0f); } UTextBlock* UAgrarianMvpFrontendWidget::AddText(UVerticalBox* Parent, const FText& Text, int32 FontSize, bool bBold, const FLinearColor& Color, float BottomPadding) { UTextBlock* TextBlock = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); TextBlock->SetText(Text); TextBlock->SetFont(FCoreStyle::GetDefaultFontStyle(bBold ? "Bold" : "Regular", FontSize)); TextBlock->SetColorAndOpacity(FSlateColor(Color)); TextBlock->SetAutoWrapText(true); TextBlock->SetJustification(ETextJustify::Left); if (Parent) { if (UVerticalBoxSlot* TextSlot = Parent->AddChildToVerticalBox(TextBlock)) { TextSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding)); } } return TextBlock; } UButton* UAgrarianMvpFrontendWidget::AddButton(UVerticalBox* Parent, const FText& Text, const FLinearColor& NormalColor, const FLinearColor& HoveredColor, float BottomPadding) { UButton* Button = WidgetTree->ConstructWidget(UButton::StaticClass()); Button->SetBackgroundColor(NormalColor); Button->SetClickMethod(EButtonClickMethod::MouseDown); Button->SetTouchMethod(EButtonTouchMethod::Down); if (!Text.IsEmpty()) { UTextBlock* Label = AddText(nullptr, Text, FMath::RoundToInt(20.0f * FMath::Clamp(UiScale, 0.75f, 1.5f)), true, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f), 0.0f); Button->SetContent(Label); if (UButtonSlot* LabelSlot = Cast(Label->Slot)) { LabelSlot->SetPadding(FMargin(22.0f, 12.0f)); LabelSlot->SetHorizontalAlignment(HAlign_Center); } } if (Parent) { if (UVerticalBoxSlot* ButtonSlot = Parent->AddChildToVerticalBox(Button)) { ButtonSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, BottomPadding)); ButtonSlot->SetHorizontalAlignment(HAlign_Left); } } return Button; } void UAgrarianMvpFrontendWidget::FocusPrimaryButton() { if (PrimaryFocusButton) { PrimaryFocusButton->SetKeyboardFocus(); } else { SetKeyboardFocus(); } } void UAgrarianMvpFrontendWidget::HandlePrimaryActionClicked() { DeferFrontendAction([this]() { ConfirmActiveScreen(); }); } void UAgrarianMvpFrontendWidget::HandleBackClicked() { DeferFrontendAction([this]() { BackFromActiveScreen(); }); } void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked() { DeferFrontendAction([this]() { SaveAndQuit(); }); } void UAgrarianMvpFrontendWidget::HandleSaveGameClicked() { DeferFrontendAction([this]() { SaveGame(); }); } void UAgrarianMvpFrontendWidget::HandleSettingsClicked() { DeferFrontendAction([this]() { SetActiveScreen(EAgrarianMvpFrontendScreen::Settings); }); } void UAgrarianMvpFrontendWidget::HandleQuitWithoutSavingClicked() { DeferFrontendAction([this]() { QuitWithoutSaving(); }); } void UAgrarianMvpFrontendWidget::HandleMaleCharacterClicked() { DeferFrontendAction([this]() { SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultMale); }); } void UAgrarianMvpFrontendWidget::HandleFemaleCharacterClicked() { DeferFrontendAction([this]() { SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale); }); } void UAgrarianMvpFrontendWidget::DeferFrontendAction(TFunction Action) { if (!Action) { return; } if (UWorld* World = GetWorld()) { FTimerDelegate DeferredAction; DeferredAction.BindLambda([WeakThis = TWeakObjectPtr(this), Action = MoveTemp(Action)]() mutable { if (WeakThis.IsValid()) { Action(); } }); World->GetTimerManager().SetTimerForNextTick(DeferredAction); return; } Action(); } FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const { return SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale ? FText::FromString(TEXT("young adult female")) : FText::FromString(TEXT("young adult male")); } FText UAgrarianMvpFrontendWidget::GetSelectedRoleLabel() const { return FText::FromString(TEXT("Pioneer")); }