Add MVP server travel flow

This commit is contained in:
2026-05-18 15:12:20 -07:00
parent 1367f5963a
commit ebc7aa1ec6
6 changed files with 147 additions and 1 deletions
+4 -1
View File
@@ -704,7 +704,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
script already exist, then added a reusable Ubuntu gameplay-server bootstrap
script and standardized the MVP endpoint as `play.agrariangame.com` on
`7777/udp`.
- [ ] Add server travel flow.
- [x] Add server travel flow. Added an allowlisted `AgrarianServerTravel
GroundZero` admin/dev command that routes through server authority and travels
to `/Game/Agrarian/Maps/L_GroundZeroTerrain_Test?listen` for repeatable MVP
listen-server and dedicated-server tests.
- [x] Define server authority over streamed terrain tiles.
- [x] Define server response when a client requests a missing tile.
- [x] Add player join flow.
+13
View File
@@ -138,6 +138,19 @@ MVP join flow:
The first MVP does not need account services, matchmaking, or full character
persistence. It does need a deterministic flow that can be tested repeatedly.
## Server Travel Flow
MVP server travel is intentionally allowlisted. The admin/dev command
`AgrarianServerTravel GroundZero` routes through the server and resolves to:
```text
/Game/Agrarian/Maps/L_GroundZeroTerrain_Test?listen
```
This gives listen-server and dedicated-server tests one repeatable travel path
without exposing arbitrary map URLs to clients. Future travel expansion should
add explicit tile/session validation before allowing any new map or region.
## Spawn And Respawn
Initial MVP spawn:
+9
View File
@@ -78,6 +78,15 @@ First cloud/server launch target:
./AgrarianGameServer L_GroundZeroTerrain_Test?listen -log -port=7777
```
In-game/admin MVP travel command:
```text
AgrarianServerTravel GroundZero
```
This command is allowlisted to the Ground Zero MVP map and should remain the
only server travel path until tile/session validation is stronger.
Systemd launch target created by the bootstrap script:
```bash
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Validate MVP server travel flow wiring."""
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
def read(relative_path: str) -> str:
path = ROOT / relative_path
if not path.exists():
raise AssertionError(f"Missing required file: {relative_path}")
return path.read_text(encoding="utf-8")
def require(content: str, needle: str, context: str) -> None:
if needle not in content:
raise AssertionError(f"Missing {needle!r} in {context}")
def main() -> int:
errors: list[str] = []
checks = {
"Source/AgrarianGame/AgrarianGamePlayerController.h": [
"void AgrarianServerTravel(FName MapName);",
"void ServerAgrarianServerTravel(FName MapName);",
],
"Source/AgrarianGame/AgrarianGamePlayerController.cpp": [
"GroundZeroServerTravelMapPath",
"ResolveAgrarianServerTravelMap",
"AgrarianServerTravel(FName MapName)",
"ServerAgrarianServerTravel_Implementation",
"HasAuthority()",
"AgrarianServerTravel GroundZero",
"World->ServerTravel(TravelURL, false, false)",
"/Game/Agrarian/Maps/L_GroundZeroTerrain_Test",
"?listen",
],
"Docs/MultiplayerNetworkingDesign.md": [
"Server Travel Flow",
"AgrarianServerTravel GroundZero",
"/Game/Agrarian/Maps/L_GroundZeroTerrain_Test?listen",
"without exposing arbitrary map URLs",
],
"Docs/Ops/DedicatedServerBuildRunbook.md": [
"AgrarianServerTravel GroundZero",
"allowlisted to the Ground Zero MVP map",
],
"AGRARIAN_DEVELOPMENT_ROADMAP.md": [
"[x] Add server travel flow.",
"AgrarianServerTravel",
"/Game/Agrarian/Maps/L_GroundZeroTerrain_Test?listen",
],
}
for relative_path, needles in checks.items():
try:
content = read(relative_path)
for needle in needles:
require(content, needle, relative_path)
except AssertionError as exc:
errors.append(str(exc))
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print("PASS: MVP server travel flow is wired and documented.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -22,6 +22,20 @@
namespace
{
const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f);
const TCHAR* GroundZeroServerTravelMapPath = TEXT("/Game/Agrarian/Maps/L_GroundZeroTerrain_Test");
bool ResolveAgrarianServerTravelMap(const FName MapName, FString& OutTravelMap)
{
if (MapName == NAME_None
|| MapName == TEXT("GroundZero")
|| MapName == TEXT("L_GroundZeroTerrain_Test"))
{
OutTravelMap = GroundZeroServerTravelMapPath;
return true;
}
return false;
}
bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary)
{
@@ -260,6 +274,11 @@ void AAgrarianGamePlayerController::AgrarianTravelHome()
ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation);
}
void AAgrarianGamePlayerController::AgrarianServerTravel(FName MapName)
{
ServerAgrarianServerTravel(MapName);
}
void AAgrarianGamePlayerController::ServerAgrarianGrantItem_Implementation(FName ItemId, int32 Quantity)
{
AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
@@ -521,3 +540,24 @@ void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector
ClientMessage(TEXT("Developer travel failed: invalid destination."));
}
}
void AAgrarianGamePlayerController::ServerAgrarianServerTravel_Implementation(FName MapName)
{
UWorld* World = GetWorld();
if (!World || !HasAuthority())
{
ClientMessage(TEXT("Agrarian server travel failed: no authoritative world."));
return;
}
FString TravelMap;
if (!ResolveAgrarianServerTravelMap(MapName, TravelMap))
{
ClientMessage(TEXT("Usage: AgrarianServerTravel GroundZero"));
return;
}
const FString TravelURL = FString::Printf(TEXT("%s?listen"), *TravelMap);
ClientMessage(FString::Printf(TEXT("Agrarian server travel requested: %s"), *TravelURL));
World->ServerTravel(TravelURL, false, false);
}
@@ -90,6 +90,9 @@ public:
UFUNCTION(Exec)
void AgrarianTravelHome();
UFUNCTION(Exec)
void AgrarianServerTravel(FName MapName);
protected:
UFUNCTION(Server, Reliable)
void ServerAgrarianGrantItem(FName ItemId, int32 Quantity);
@@ -120,4 +123,7 @@ protected:
UFUNCTION(Server, Reliable)
void ServerAgrarianTravel(FVector Destination);
UFUNCTION(Server, Reliable)
void ServerAgrarianServerTravel(FName MapName);
};