From 6eb262acc357f3abf4e3a8164b3ad3a5bd77afb2 Mon Sep 17 00:00:00 2001 From: Nathan Slaven Date: Fri, 22 May 2026 03:06:27 +0000 Subject: [PATCH] Add pause save exit settings shell --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 13 ++ .../verify_mvp_menu_input_and_quit_flow.py | 16 ++- ...verify_mvp_segmented_startup_pause_flow.py | 13 ++ .../AgrarianMvpFrontendWidget.cpp | 125 +++++++++++++++++- .../AgrarianGame/AgrarianMvpFrontendWidget.h | 19 +++ 5 files changed, 182 insertions(+), 4 deletions(-) diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 00ef4d1..f8a5149 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -950,6 +950,7 @@ Required order: - [x] Replace or upgrade grasses, shrubs, and trees with believable coastal-scrub vegetation assets, density, color variation, scale variation, and LOD/performance limits. Added native generated coastal oak, coyote brush, and dry grass clump mesh assets under `/Game/Agrarian/Environment/Vegetation`, switched the Ground Zero foliage patch off engine basic shapes, rebuilt foliage materials with per-instance color variation, preserved investor-facing density and scale variation, added explicit HISM cull/shadow performance limits, and extended verifiers so basic-shape vegetation or missing cull limits fail. - [x] Add the Asset acquisition and ingest pipeline before pulling more visuals: created approved staging folders, added `Docs/Art/AssetLicenses.md`, documented the pipeline in `Docs/Art/AgrarianAssetPipeline.md`, added `Scripts/verify_asset_pipeline_policy.py`, defaulted to Fab/free, Quixel, CC0/public-domain, team-created, or Nathan-supplied assets only, rejected random scraped internet assets, and prioritized trees, shrubs, grass, water, rocks, character bodies/outfits, and old abandoned equipment being reclaimed by nature. - [x] Add the Ground Zero asset acquisition queue for the investor visual pass: documented free-only Fab candidates for shrubs, Mediterranean/coastal plants, grass, rocks/water-support props, and rural/reclaimed set dressing in `Docs/Art/GroundZeroAssetAcquisitionQueue.md`; added `Scripts/verify_ground_zero_asset_queue.py`; tied every candidate to the license register and staging workflow. +- [x] Add stable MVP pause menu save/exit/settings shell: Resume, Save Game, Settings, Save & Exit, and Quit Without Saving now have separate player-facing actions, keyboard shortcuts, and verifier coverage while deeper settings stay roadmapped. - [ ] Replace or upgrade freshwater visuals with readable water surface, edge treatment, bank dressing, reflection/roughness tuning, and collectability cues. - [ ] Replace or upgrade character bodies and clothing so selected characters read as realistic near-future post-collapse frontier people rather than template mannequins or proxy stacks. - [ ] Replace or upgrade resource objects so wood, stone, fiber, edible plants, pickups, and gathered items look like world objects rather than debug primitives. @@ -1058,6 +1059,18 @@ Required order: - [ ] Add early business knowledge for bookkeeping, inventory, profit/loss, fair trade, basic credit, risk, and customer trust. - [ ] Add simple workshop/business ownership rules for homestead-scale production. +## 0.2.F1 Player Options And Settings + +- [ ] Define the settings persistence model so options survive save/load, packaged demos, and future multiplayer profile storage. +- [ ] Add preferred units for metric/imperial distance, weight, temperature, speed, volume, and field-size display. +- [ ] Add controls remapping UI for keyboard, mouse, and gamepad while preserving sane defaults for movement, sprint, crouch, prone, interact, menus, and camera. +- [ ] Add gameplay settings for autosave cadence, UI scale, hints, camera behavior, interaction prompts, and accessibility-friendly timing. +- [ ] Add graphics and hardware settings for quality presets, resolution/window mode, frame cap, foliage density, shadows, water, post-process, ray tracing optional toggles, and reset-to-safe defaults. +- [ ] Add audio settings for master, music, effects, ambient, voice, and cinematic volume. +- [ ] Add accessibility settings for subtitles, color/contrast, text scale, hold-versus-toggle interactions, motion comfort, and input assistance. +- [ ] Add account/server preferences for default server, last-used address, privacy-safe telemetry choice, and multiplayer connection display. +- [ ] Add verifier coverage for settings save/load, default migration, reset-to-default behavior, and packaged-demo menu access. + ## 0.2.G Homesteading Knowledge Progression - [ ] Define early profession paths: farmer, herder, carpenter, mason, cook, medic, hunter, fisher, trapper, trader, and scout. diff --git a/Scripts/verify_mvp_menu_input_and_quit_flow.py b/Scripts/verify_mvp_menu_input_and_quit_flow.py index bc96fa3..792e3ee 100644 --- a/Scripts/verify_mvp_menu_input_and_quit_flow.py +++ b/Scripts/verify_mvp_menu_input_and_quit_flow.py @@ -13,15 +13,29 @@ EXPECTED = { "AgrarianMvpFrontendWidget.h": [ "ConfirmActiveScreen", "BackFromActiveScreen", + "SaveGame", "SaveAndQuit", + "QuitWithoutSaving", + "Settings", + "GameSaved", ], "AgrarianMvpFrontendWidget.cpp": [ "UButton::StaticClass()", "HandleMaleCharacterClicked", "HandleFemaleCharacterClicked", "OnClicked.AddDynamic", - "Save & Quit", + "Save Game", + "Settings", + "Save & Exit", + "Quit Without Saving", "Saving World", + "Game Saved", + "Player Options", + "HandleSaveGameClicked", + "HandleSettingsClicked", + "HandleQuitWithoutSavingClicked", + "ExecuteSaveGame", + "ExecuteQuitWithoutSaving", "ConsoleCommand(TEXT(\"AgrarianSaveWorld\"))", "ConsoleCommand(TEXT(\"quit\"))", "AAgrarianGamePlayerController* AgrarianPlayerController", diff --git a/Scripts/verify_mvp_segmented_startup_pause_flow.py b/Scripts/verify_mvp_segmented_startup_pause_flow.py index 84148b3..a1b1633 100644 --- a/Scripts/verify_mvp_segmented_startup_pause_flow.py +++ b/Scripts/verify_mvp_segmented_startup_pause_flow.py @@ -23,8 +23,12 @@ def main() -> None: roadmap = ROADMAP.read_text(encoding="utf-8") for token in ( + "Settings", + "GameSaved", "SavingAndQuit", + "SaveGame", "ExecuteSaveAndQuit", + "QuitWithoutSaving", ): require(token in header, f"missing segmented flow declaration: {token}") @@ -34,11 +38,20 @@ def main() -> None: "Loading Segment", "Pause Menu", "Gameplay is paused while this menu is active.", + "Save Game", + "Settings", + "Quit Without Saving", + "Game Saved", + "Player Options", "Saving World", "Writing the current world state", + "SetActiveScreen(EAgrarianMvpFrontendScreen::GameSaved)", + "SetActiveScreen(EAgrarianMvpFrontendScreen::Settings)", "SetActiveScreen(EAgrarianMvpFrontendScreen::SavingAndQuit)", "GetTimerManager().SetTimer", + "ExecuteSaveGame", "ExecuteSaveAndQuit", + "ExecuteQuitWithoutSaving", "ConsoleCommand(TEXT(\"AgrarianSaveWorld\"))", "ConsoleCommand(TEXT(\"quit\"))", ): diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp index 2db51a9..059a4b1 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp @@ -87,6 +87,33 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, 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) { @@ -147,6 +174,17 @@ void UAgrarianMvpFrontendWidget::SaveAndQuit() ExecuteSaveAndQuit(); } +void UAgrarianMvpFrontendWidget::SaveGame() +{ + ExecuteSaveGame(); + SetActiveScreen(EAgrarianMvpFrontendScreen::GameSaved); +} + +void UAgrarianMvpFrontendWidget::QuitWithoutSaving() +{ + ExecuteQuitWithoutSaving(); +} + void UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit() { if (APlayerController* PlayerController = GetOwningPlayer()) @@ -156,6 +194,22 @@ void UAgrarianMvpFrontendWidget::ExecuteSaveAndQuit() } } +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) @@ -181,6 +235,12 @@ void UAgrarianMvpFrontendWidget::ContinueFromActiveScreen() return; } + if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings || ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); + return; + } + if (ActiveScreen == EAgrarianMvpFrontendScreen::MainMenu) { CompleteFrontendFlow(); @@ -200,6 +260,11 @@ void UAgrarianMvpFrontendWidget::ReturnFromActiveScreen() { CompleteFrontendFlow(); } + + if (ActiveScreen == EAgrarianMvpFrontendScreen::Settings || ActiveScreen == EAgrarianMvpFrontendScreen::GameSaved) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::MainMenu); + } } void UAgrarianMvpFrontendWidget::CompleteFrontendFlow() @@ -274,13 +339,23 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree() { 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, 72.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* QuitButton = AddButton(Panel, FText::FromString(TEXT("Save & Quit")), QuitButtonColor, FLinearColor(0.58f, 0.28f, 0.22f, 1.0f), 34.0f * Scale); + 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); - AddText(Panel, FText::FromString(TEXT("Escape opens this menu. Save & Quit writes the current world save before closing.")), FMath::RoundToInt(16.0f * Scale), false, MutedTextColor, 0.0f); + + 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; } @@ -390,6 +465,26 @@ void UAgrarianMvpFrontendWidget::RebuildFrontendTree() 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); @@ -485,6 +580,30 @@ void UAgrarianMvpFrontendWidget::HandleSaveAndQuitClicked() }); } +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]() diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h index 00579e7..778204f 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h @@ -17,6 +17,8 @@ enum class EAgrarianMvpFrontendScreen : uint8 CharacterSelection, JoinServer, Loading, + Settings, + GameSaved, SavingAndQuit }; @@ -75,9 +77,15 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") void BackFromActiveScreen(); + UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") + void SaveGame(); + UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") void SaveAndQuit(); + UFUNCTION(BlueprintCallable, Category = "Agrarian|MVP UI") + void QuitWithoutSaving(); + protected: virtual void NativeConstruct() override; @@ -96,6 +104,8 @@ private: UFUNCTION() void ExecuteSaveAndQuit(); + void ExecuteSaveGame(); + void ExecuteQuitWithoutSaving(); UFUNCTION() void HandlePrimaryActionClicked(); @@ -106,6 +116,15 @@ private: UFUNCTION() void HandleSaveAndQuitClicked(); + UFUNCTION() + void HandleSaveGameClicked(); + + UFUNCTION() + void HandleSettingsClicked(); + + UFUNCTION() + void HandleQuitWithoutSavingClicked(); + UFUNCTION() void HandleMaleCharacterClicked();