Add MVP server travel flow
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user