From e8d46c8238c8b0810cc954cf5819c4db41a96f9b Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 18 May 2026 21:04:25 -0700 Subject: [PATCH] Add MVP loading screen --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 2 +- Scripts/verify_mvp_loading_screen.py | 52 +++++++++++++++++++ .../AgrarianGamePlayerController.cpp | 9 +++- .../AgrarianMvpFrontendWidget.cpp | 49 +++++++++++++++++ .../AgrarianGame/AgrarianMvpFrontendWidget.h | 8 +++ 5 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 Scripts/verify_mvp_loading_screen.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 3bb7ec9..c2f44db 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -797,7 +797,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] After splash/startup screens, land on an MVP character selection landing page. The local player controller now opens the MVP frontend on `CharacterSelection`, and the native frontend widget paints a scalable character-selection landing page before gameplay. - [x] Let players choose a realistic young adult male or female character with average proportions for the MVP. Added a selectable MVP character archetype enum, visible selected state, keyboard selection, and `AgrarianSelectCharacter male|female` dev command while keeping both choices on the same MVP survival baseline until real character art arrives. - [x] Add join server screen. Added a native `JoinServer` frontend screen showing the selected character and `play.agrariangame.com:7777`, with keyboard flow from character selection and a dev command to display the screen directly. -- [ ] Add loading screen. +- [x] Add loading screen. Added a native `Loading` frontend screen with Ground Zero preparation copy, selected character/server context, a deterministic placeholder progress bar, and join-screen keyboard flow into loading. - [~] Add HUD. - [ ] Add inventory UI. - [ ] Add crafting UI. diff --git a/Scripts/verify_mvp_loading_screen.py b/Scripts/verify_mvp_loading_screen.py new file mode 100644 index 0000000..1cde523 --- /dev/null +++ b/Scripts/verify_mvp_loading_screen.py @@ -0,0 +1,52 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "AgrarianMvpFrontendWidget.h": ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.h", + "AgrarianMvpFrontendWidget.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianMvpFrontendWidget.cpp", + "AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "AgrarianMvpFrontendWidget.h": [ + "Loading", + "DrawLoading", + ], + "AgrarianMvpFrontendWidget.cpp": [ + "EAgrarianMvpFrontendScreen::Loading", + "SetActiveScreen(EAgrarianMvpFrontendScreen::Loading)", + "Preparing Ground Zero", + "Loading terrain, weather, survival state, and server session data.", + "MVP loading placeholder", + "BarSize.X * 0.62f", + ], + "AgrarianGamePlayerController.cpp": [ + "MVP frontend screen: loading.", + "Usage: AgrarianShowMvpScreen main|character|join|loading", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add loading screen.", + "`Loading`", + "Ground Zero preparation", + ], +} + + +def main(): + missing = [] + for label, path in FILES.items(): + text = path.read_text(encoding="utf-8") + for snippet in EXPECTED[label]: + if snippet not in text: + missing.append(f"{label}: {snippet}") + + if missing: + raise RuntimeError("MVP loading screen verification failed: " + "; ".join(missing)) + + print("Agrarian MVP loading screen verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.cpp b/Source/AgrarianGame/AgrarianGamePlayerController.cpp index f8987ab..e66bdb7 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.cpp +++ b/Source/AgrarianGame/AgrarianGamePlayerController.cpp @@ -334,7 +334,14 @@ void AAgrarianGamePlayerController::AgrarianShowMvpScreen(FName ScreenName) return; } - ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join")); + if (ScreenName == TEXT("loading") || ScreenName == TEXT("Loading")) + { + MvpFrontendWidget->SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); + ClientMessage(TEXT("MVP frontend screen: loading.")); + return; + } + + ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join|loading")); } void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z) diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp index b312c8e..42ceb1a 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.cpp @@ -39,6 +39,12 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry, else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer) { const FKey Key = InKeyEvent.GetKey(); + if (Key == EKeys::Enter || Key == EKeys::SpaceBar) + { + SetActiveScreen(EAgrarianMvpFrontendScreen::Loading); + return FReply::Handled(); + } + if (Key == EKeys::BackSpace || Key == EKeys::Escape) { SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection); @@ -118,6 +124,10 @@ int32 UAgrarianMvpFrontendWidget::NativePaint( { DrawJoinServer(OutDrawElements, LayerId, AllottedGeometry, PanelPosition, PanelSize, Scale); } + else if (ActiveScreen == EAgrarianMvpFrontendScreen::Loading) + { + DrawLoading(OutDrawElements, LayerId, AllottedGeometry, PanelPosition, PanelSize, Scale); + } return LayerId; } @@ -271,6 +281,45 @@ void UAgrarianMvpFrontendWidget::DrawJoinServer( DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Press Backspace or Escape to return to character selection.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); } +void UAgrarianMvpFrontendWidget::DrawLoading( + FSlateWindowElementList& OutDrawElements, + int32& LayerId, + const FGeometry& AllottedGeometry, + const FVector2D& PanelPosition, + const FVector2D& PanelSize, + float Scale) const +{ + const float ContentX = PanelPosition.X + (48.0f * Scale); + const float ContentWidth = PanelSize.X - (96.0f * Scale); + const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", FMath::RoundToInt(34.0f * Scale)); + const FSlateFontInfo BodyFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(18.0f * Scale)); + const FSlateFontInfo HintFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(15.0f * Scale)); + + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Preparing Ground Zero")), FVector2D(ContentX, PanelPosition.Y + (58.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Loading terrain, weather, survival state, and server session data.")), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (108.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f)); + + const FVector2D BarPosition(ContentX, PanelPosition.Y + (214.0f * Scale)); + const FVector2D BarSize(ContentWidth, 18.0f * Scale); + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(BarSize), FSlateLayoutTransform(FVector2f(BarPosition))), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.065f, 0.075f, 0.06f, 1.0f)); + + FSlateDrawElement::MakeBox( + OutDrawElements, + ++LayerId, + AllottedGeometry.ToPaintGeometry(FVector2f(BarSize.X * 0.62f, BarSize.Y), FSlateLayoutTransform(FVector2f(BarPosition))), + FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")), + ESlateDrawEffect::None, + FLinearColor(0.45f, 0.72f, 0.40f, 1.0f)); + + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Character: {0} | Server: {1}")), GetSelectedCharacterLabel(), JoinServerAddress), FVector2D(ContentX, PanelPosition.Y + (270.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.78f, 0.84f, 0.72f, 1.0f)); + DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("MVP loading placeholder; final connection and map handoff will attach here.")), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, HintFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f)); +} + FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const { return SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale diff --git a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h index aae2eaa..e426108 100644 --- a/Source/AgrarianGame/AgrarianMvpFrontendWidget.h +++ b/Source/AgrarianGame/AgrarianMvpFrontendWidget.h @@ -94,6 +94,14 @@ private: const FVector2D& PanelSize, float Scale) const; + void DrawLoading( + FSlateWindowElementList& OutDrawElements, + int32& LayerId, + const FGeometry& AllottedGeometry, + const FVector2D& PanelPosition, + const FVector2D& PanelSize, + float Scale) const; + FText GetSelectedCharacterLabel() const; void DrawTextAt(