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 already exist, then added a reusable Ubuntu gameplay-server bootstrap
|
||||||
script and standardized the MVP endpoint as `play.agrariangame.com` on
|
script and standardized the MVP endpoint as `play.agrariangame.com` on
|
||||||
`7777/udp`.
|
`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 authority over streamed terrain tiles.
|
||||||
- [x] Define server response when a client requests a missing tile.
|
- [x] Define server response when a client requests a missing tile.
|
||||||
- [x] Add player join flow.
|
- [x] Add player join flow.
|
||||||
|
|||||||
@@ -138,6 +138,19 @@ MVP join flow:
|
|||||||
The first MVP does not need account services, matchmaking, or full character
|
The first MVP does not need account services, matchmaking, or full character
|
||||||
persistence. It does need a deterministic flow that can be tested repeatedly.
|
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
|
## Spawn And Respawn
|
||||||
|
|
||||||
Initial MVP spawn:
|
Initial MVP spawn:
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ First cloud/server launch target:
|
|||||||
./AgrarianGameServer L_GroundZeroTerrain_Test?listen -log -port=7777
|
./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:
|
Systemd launch target created by the bootstrap script:
|
||||||
|
|
||||||
```bash
|
```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
|
namespace
|
||||||
{
|
{
|
||||||
const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f);
|
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)
|
bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary)
|
||||||
{
|
{
|
||||||
@@ -260,6 +274,11 @@ void AAgrarianGamePlayerController::AgrarianTravelHome()
|
|||||||
ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation);
|
ServerAgrarianTravel(GroundZeroDeveloperTravelHomeLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AAgrarianGamePlayerController::AgrarianServerTravel(FName MapName)
|
||||||
|
{
|
||||||
|
ServerAgrarianServerTravel(MapName);
|
||||||
|
}
|
||||||
|
|
||||||
void AAgrarianGamePlayerController::ServerAgrarianGrantItem_Implementation(FName ItemId, int32 Quantity)
|
void AAgrarianGamePlayerController::ServerAgrarianGrantItem_Implementation(FName ItemId, int32 Quantity)
|
||||||
{
|
{
|
||||||
AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
|
AAgrarianGameCharacter* AgrarianCharacter = GetPawn<AAgrarianGameCharacter>();
|
||||||
@@ -521,3 +540,24 @@ void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector
|
|||||||
ClientMessage(TEXT("Developer travel failed: invalid destination."));
|
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)
|
UFUNCTION(Exec)
|
||||||
void AgrarianTravelHome();
|
void AgrarianTravelHome();
|
||||||
|
|
||||||
|
UFUNCTION(Exec)
|
||||||
|
void AgrarianServerTravel(FName MapName);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UFUNCTION(Server, Reliable)
|
UFUNCTION(Server, Reliable)
|
||||||
void ServerAgrarianGrantItem(FName ItemId, int32 Quantity);
|
void ServerAgrarianGrantItem(FName ItemId, int32 Quantity);
|
||||||
@@ -120,4 +123,7 @@ protected:
|
|||||||
|
|
||||||
UFUNCTION(Server, Reliable)
|
UFUNCTION(Server, Reliable)
|
||||||
void ServerAgrarianTravel(FVector Destination);
|
void ServerAgrarianTravel(FVector Destination);
|
||||||
|
|
||||||
|
UFUNCTION(Server, Reliable)
|
||||||
|
void ServerAgrarianServerTravel(FName MapName);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user