MotaEngine

Online Services

Self-hosted live services for MotaEngine games: a standalone MotawareOnlineServer process plus the engine's HttpOnlineProvider, the networked implementation of the engine's IOnlineSubsystem interfaces. Everything the offline LocalStub provider simulates in-process round-trips to a server you run — leaderboards, parties, presence, sessions, cloud saves and the rest are shared across every connected machine.

game / engine code
      │  OnlineSubsystemRegistry::Get()
      ▼
IOnlineSubsystem ──────────────┬──────────────────────────────┐
                               │                              │
                    LocalStubProvider              HttpOnlineProvider     
                    (offline, per-machine,         REST/JSON over HTTP
                     Saved/Online on disk)                 │
                                                           ▼
                                              MotawareOnlineServer
                                              (tools/online-server;
                                               state.json + cloud blobs)

A future Steam/EOS port implements the same interfaces against the vendor SDK and swaps in at the same seam; nothing above the registry changes.

Using it from the engine

MotaEngine  --online-url=http://my-server:27080 [--online-api-key=SECRET]
MotaServer  --online-url=http://my-server:27080 ...

Without --online-url the engine registers the LocalStub exactly as before. With it, the HttpOnlineProvider is registered even when the server is unreachable at boot (matching vendor-SDK behaviour); its IsAvailable() health check re-runs on demand (cached 5 s), and the online status console command shows the live verdict.

Semantics follow the LocalStub contracts, with the documented adaptations (see http_online_provider.h): auth is uid+displayName for a bearer token (no passwords — LocalStub's trust model, centralised); transport failures degrade to the interface's failure returns; the matchmaking callback fires for this user's matches drained from a server-side inbox; achievement definitions and the IAP catalog stay client-side; voice remains a local state machine.

The server

MotawareOnlineServer [--port=27080] [--bind=0.0.0.0]
                     [--data-dir=online-data] [--api-key=SECRET]
  • State: one atomically-rewritten state.json under --data-dir (saved after every mutation), plus cloud payloads as cloud/<uid>/<slot>.bin. Matchmaking tickets and bearer tokens are deliberately in-memory: a restart empties the queue and signs everyone out, and clients re-sign-in transparently on the first 401.
  • Auth: optional shared API key via the X-Mota-Api-Key header gates every route except /v1/health. This is engine-grade auth for a server you control, not internet-grade identity.
  • TLS: the server speaks plain HTTP. For anything beyond a LAN, front it with a reverse proxy (nginx/caddy) that terminates TLS — note the engine's cpp-httplib is built without SSL, so the proxy must also accept HTTP from the game or you terminate TLS at a trusted network boundary.

Endpoints (/v1, JSON unless noted)

AreaMethod + pathNotes
HealthGET /healthno auth
AuthPOST /auth/signin {uid, displayName} → {token}; POST /auth/signout
IdentityGET /identity/me; GET/POST /identity/friends; DELETE /identity/friends/<uid>
Achievements`POST /achievements/unlockprogress {id, maxProgress[, progress]}; GET /achievements/state`
LeaderboardsPOST /leaderboards/define; GET /leaderboards; POST /leaderboards/<id>/scores; GET .../top?n=, .../around?count=, .../rankkeep-best per user; ties: earlier submit wins
Cloud savesGET /cloud; `PUTGET
PresencePOST /presence {state}; `GET /presence/me`
Recent players`POSTGET
PartiesPOST /parties; GET /parties/current; POST /parties/leave; `POSTGET /parties/invites; POST /parties/invites//accept; POST
Matchmaking`POSTGET /matchmaking/tickets; DELETE /matchmaking/tickets/; POST /matchmaking/process`
Sessions`POSTGET /sessions; GET
IAPPOST /iap/purchase {productId, type}; GET /iap/entitlements; POST /iap/consumeproduct types enforced client-side (catalog is game content)

Everything except /health and /auth/signin requires Authorization: Bearer <token>.

Building the server

With the engine (default when configuring the repo top-level; -DMOTA_BUILD_ONLINE_SERVER=OFF to skip): the exe lands next to the other tools at build/tools/online-server/<config>/MotawareOnlineServer.exe, and MotaOnlineServerCore is linked into MotaTests so the whole protocol runs in-process in the test suite (tests/test_online_http.cpp, tag [m6][online-http]).

Standalone on a deployment host (typically Linux — the server is plain C++20 + std::filesystem with no engine dependency):

sudo apt install -y build-essential cmake git        # Ubuntu 22.04+
git clone <repo> motaware && cd motaware
cmake -S tools/online-server -B build-online -DCMAKE_BUILD_TYPE=Release
cmake --build build-online -j
./build-online/MotawareOnlineServer --port=27080 --data-dir=/var/lib/motaware-online

find_package is tried for nlohmann-json and cpp-httplib first (sudo apt install nlohmann-json3-dev to use the distro package); whatever is missing is fetched at configure time via FetchContent, so a bare host with a compiler, CMake ≥ 3.21 and network access builds without any package setup.

systemd unit (Ubuntu)

# /etc/systemd/system/motaware-online.service
[Unit]
Description=Motaware online services
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/opt/motaware/MotawareOnlineServer --port=27080 \
    --data-dir=/var/lib/motaware-online --api-key=CHANGE_ME
Restart=on-failure
User=motaware
DynamicUser=yes
StateDirectory=motaware-online

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now motaware-online
curl http://localhost:27080/v1/health

Testing

  • MotaTests "[m6][online-http]" — the end-to-end suite: real server core behind a real cpp-httplib listener on an ephemeral loopback port, driven through the real provider. Covers every service, two-client cross-visibility (leaderboards, invites, presence, matchmaking, sessions), binary cloud-save round-trips with hash verification, and a server-restart test proving state.json persistence plus the transparent 401 re-sign-in.
  • Handler-level tests hit OnlineServerCore::Dispatch directly with no port bound — the same split as the streaming module's signaling server.

Known limits

  • No passwords / real identity — the server trusts the uid a client presents (plus the shared API key). Fine for a server you run for your own game's players during development; a public launch wants real auth in front.
  • IAP has no payment processor — purchases always succeed server-side; the entitlement store is real, the storefront is not.
  • Matchmaking process is client-driven (any client's poll advances the queue) rather than a server-side background matcher.
  • Voice is a local state machine (the vendor-SDK seam) — no relay.