Add MVP join server screen

This commit is contained in:
2026-05-18 21:02:21 -07:00
parent c855924034
commit 2009295c18
6 changed files with 165 additions and 1 deletions
+1 -1
View File
@@ -796,7 +796,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add main menu placeholder. Added native `UAgrarianMvpFrontendWidget` with a scalable painted main menu panel and local player-controller spawning so the MVP frontend has an owned placeholder instead of relying on template UI.
- [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.
- [ ] Add join server screen.
- [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.
- [~] Add HUD.
- [ ] Add inventory UI.
+57
View File
@@ -0,0 +1,57 @@
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.h": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.h",
"AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp",
"AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md",
}
EXPECTED = {
"AgrarianMvpFrontendWidget.h": [
"JoinServer",
"JoinServerAddress",
"play.agrariangame.com:7777",
"DrawJoinServer",
],
"AgrarianMvpFrontendWidget.cpp": [
"EAgrarianMvpFrontendScreen::JoinServer",
"SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer)",
"Join MVP server",
"Continue to loading",
],
"AgrarianGamePlayerController.h": [
"AgrarianShowMvpScreen",
],
"AgrarianGamePlayerController.cpp": [
"AgrarianShowMvpScreen",
"SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer)",
"Usage: AgrarianShowMvpScreen main|character|join",
],
"AGRARIAN_DEVELOPMENT_ROADMAP.md": [
"[x] Add join server screen.",
"`JoinServer`",
"play.agrariangame.com:7777",
],
}
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 join server screen verification failed: " + "; ".join(missing))
print("Agrarian MVP join server screen verification complete.")
if __name__ == "__main__":
main()
@@ -305,6 +305,38 @@ void AAgrarianGamePlayerController::AgrarianSelectCharacter(FName Archetype)
ClientMessage(TEXT("Usage: AgrarianSelectCharacter male|female"));
}
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;
}
ClientMessage(TEXT("Usage: AgrarianShowMvpScreen main|character|join"));
}
void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z)
{
ServerAgrarianTravel(FVector(X, Y, Z));
@@ -94,6 +94,9 @@ public:
UFUNCTION(Exec)
void AgrarianSelectCharacter(FName Archetype);
UFUNCTION(Exec)
void AgrarianShowMvpScreen(FName ScreenName);
UFUNCTION(Exec)
void AgrarianTravel(float X, float Y, float Z);
@@ -29,6 +29,21 @@ FReply UAgrarianMvpFrontendWidget::NativeOnKeyDown(const FGeometry& InGeometry,
SetSelectedCharacterArchetype(EAgrarianMvpCharacterArchetype::YoungAdultFemale);
return FReply::Handled();
}
if (Key == EKeys::Enter || Key == EKeys::SpaceBar)
{
SetActiveScreen(EAgrarianMvpFrontendScreen::JoinServer);
return FReply::Handled();
}
}
else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer)
{
const FKey Key = InKeyEvent.GetKey();
if (Key == EKeys::BackSpace || Key == EKeys::Escape)
{
SetActiveScreen(EAgrarianMvpFrontendScreen::CharacterSelection);
return FReply::Handled();
}
}
return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
@@ -99,6 +114,10 @@ int32 UAgrarianMvpFrontendWidget::NativePaint(
{
DrawCharacterSelection(OutDrawElements, LayerId, AllottedGeometry, PanelPosition, PanelSize, Scale);
}
else if (ActiveScreen == EAgrarianMvpFrontendScreen::JoinServer)
{
DrawJoinServer(OutDrawElements, LayerId, AllottedGeometry, PanelPosition, PanelSize, Scale);
}
return LayerId;
}
@@ -207,6 +226,51 @@ void UAgrarianMvpFrontendWidget::DrawCharacterSelection(
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Selected: {0}. Use Left/Right or A/D to choose; both choices enter the same MVP survival baseline.")), GetSelectedCharacterLabel()), FVector2D(ContentX, PanelPosition.Y + PanelSize.Y - (46.0f * Scale)), ContentWidth, LabelFont, FLinearColor(0.62f, 0.68f, 0.58f, 1.0f));
}
void UAgrarianMvpFrontendWidget::DrawJoinServer(
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 AddressFont = FCoreStyle::GetDefaultFontStyle("Bold", FMath::RoundToInt(24.0f * Scale));
const FSlateFontInfo HintFont = FCoreStyle::GetDefaultFontStyle("Regular", FMath::RoundToInt(15.0f * Scale));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Join MVP server")), FVector2D(ContentX, PanelPosition.Y + (44.0f * Scale)), ContentWidth, TitleFont, FLinearColor(0.92f, 0.98f, 0.84f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::Format(FText::FromString(TEXT("Character: {0}")), GetSelectedCharacterLabel()), FVector2D(ContentX + (2.0f * Scale), PanelPosition.Y + (94.0f * Scale)), ContentWidth, BodyFont, FLinearColor(0.72f, 0.80f, 0.68f, 1.0f));
const FVector2D AddressPanelPosition(ContentX, PanelPosition.Y + (164.0f * Scale));
const FVector2D AddressPanelSize(ContentWidth, 96.0f * Scale);
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(AddressPanelSize), FSlateLayoutTransform(FVector2f(AddressPanelPosition))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.055f, 0.065f, 0.05f, 0.96f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Server address")), AddressPanelPosition + FVector2D(24.0f * Scale, 14.0f * Scale), ContentWidth - (48.0f * Scale), HintFont, FLinearColor(0.58f, 0.64f, 0.54f, 1.0f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, JoinServerAddress, AddressPanelPosition + FVector2D(24.0f * Scale, 42.0f * Scale), ContentWidth - (48.0f * Scale), AddressFont, FLinearColor(0.86f, 0.94f, 0.78f, 1.0f));
const FVector2D ButtonPosition(ContentX, PanelPosition.Y + (300.0f * Scale));
const FVector2D ButtonSize(FMath::Min(ContentWidth, 330.0f * Scale), 58.0f * Scale);
FSlateDrawElement::MakeBox(
OutDrawElements,
++LayerId,
AllottedGeometry.ToPaintGeometry(FVector2f(ButtonSize), FSlateLayoutTransform(FVector2f(ButtonPosition))),
FCoreStyle::Get().GetBrush(TEXT("WhiteBrush")),
ESlateDrawEffect::None,
FLinearColor(0.35f, 0.58f, 0.30f, 0.95f));
DrawTextAt(OutDrawElements, LayerId, AllottedGeometry, FText::FromString(TEXT("Continue to loading")), ButtonPosition + FVector2D(22.0f * Scale, 12.0f * Scale), ButtonSize.X - (44.0f * Scale), AddressFont, FLinearColor(0.96f, 1.0f, 0.90f, 1.0f));
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));
}
FText UAgrarianMvpFrontendWidget::GetSelectedCharacterLabel() const
{
return SelectedCharacterArchetype == EAgrarianMvpCharacterArchetype::YoungAdultFemale
@@ -86,6 +86,14 @@ private:
const FVector2D& PanelSize,
float Scale) const;
void DrawJoinServer(
FSlateWindowElementList& OutDrawElements,
int32& LayerId,
const FGeometry& AllottedGeometry,
const FVector2D& PanelPosition,
const FVector2D& PanelSize,
float Scale) const;
FText GetSelectedCharacterLabel() const;
void DrawTextAt(