chore(game-server): merge main (emulator fixes: contracts, shop, fortuna, friends, admin, presence) #16

Merged
neckfire merged 35 commits from main into game-server 2026-07-16 08:25:21 +00:00
Owner
No description provided.
neckfire added 35 commits 2026-07-16 08:25:18 +00:00
feat(squad): in-memory squad/lobby state + wire squad functions
Build & Deploy / build (push) Successful in 31s
0588849e4c
Adds SquadService (singleton, process-local) tracking squads by the
squadId the client shares across party members, plus a user->squad index
so TryGetCompleteSquadInfo (no squadId in request) can resolve the
caller's squad. Members are lazily registered on any squad call.

- TryGetCompleteSquadInfo: returns the caller's real squad.
- SquadMemberReadyForMatch: joins the squad, records readiness/map,
  returns the squad + whether everyone is ready.
- SquadMemberSelectedMap: records the member's selected map.
- DbUserService.FindByIdAsync: resolve display name for squad members.

Groundwork for the station lobby; assumes the client drives squads via
these calls with a shared squadId (to confirm with a 2-client session).
Real-time SignalR push (unknown event names) deferred; the squad is
carried in responses + TryGetCompleteSquadInfo for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New console utility (net8.0, Linux/Proton + Windows) to point a client at
a server by writing backend.txt — the single value the Prospect agent
reads. Presets for PROD/PREPROD LAN and PROD internet, plus custom URL,
certificate import (native on Windows, guided with Proton-prefix
detection on Linux) and game launch. Interactive menu + scriptable CLI
(--folder/--set). Added to the solution; not built by the server CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(tool): use public domains as presets (works LAN + internet)
Build & Deploy / build (push) Successful in 26s
2cc90a22e0
Switcher presets now use the public domains (tc.nfteam.ovh:8443 /
rd-tc.nfteam.ovh:8444) so the same target works for everyone; the
unified TLS cert covers both hostnames. LAN users add a hosts entry to
avoid hairpin NAT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(tool): README for ProspectServerSwitcher
Build & Deploy / build (push) Successful in 26s
2809033b76
Documents the cross-platform server switcher: backend.txt mechanism,
domain presets, interactive/CLI usage, cert import (Windows / Proton),
launch, settings location and self-contained publish commands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(signalr): return LAN SignalR URL to local clients
Build & Deploy / build (push) Successful in 34s
0089833b74
GetSignalRConnection now serves the SignalR URL on 192.168.1.136 to
clients whose remote IP is on the LAN, and keeps the public domain for
external clients. The real-time WebSocket (libwebsockets, incl. under
Proton/Wine) couldn't reach the public domain from inside the LAN
(hairpin NAT) — it failed right after the intro as "error code 5".
External friends are unaffected (still get the domain).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore(signalr): log remote IP + resolved URL for diagnostics
Build & Deploy / build (push) Successful in 30s
6d77bb4b82
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(tool): import cert into the Proton prefix on Linux
Build & Deploy / build (push) Successful in 28s
61e45351cd
On Linux the tool now actually trusts the server certificate inside the
game's Proton/Wine prefix (the wine certutil tool is broken under Proton):
it fetches the live cert from the server (backend.txt), builds the wine
serialized cert blob (SHA1 + encoded cert) and writes it to the prefix
Root store via `wine reg import`. Auto-detects the Proton wine and lists
compatdata prefixes (most-recent first) so the non-Steam-shortcut prefix
is picked correctly; the chosen prefix is remembered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(tool): document automatic Proton prefix cert import
Build & Deploy / build (push) Successful in 28s
c70f674431
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(social): disable friends limit (was capping at 0 friends)
Build & Deploy / build (push) Successful in 31s
8057fcae47
FeatureToggles had m_isFriendsLimitEnabled=true with no positive limit,
so the client showed 'friend limit reached: 0 friends' and blocked adding
friends / squad invites. Disabled the toggle (squads/social already on).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EnterMatchmakingMatch broadcast OnSquadMatchmakingSuccess to Clients.All,
so one player deploying pulled every connected player into the match.
Map each player to their SignalR connection (GetSignalRConnection tags the
URL with uid; CycleHub records it via a SignalRConnectionRegistry) and
send the deploy signal only to that connection (broadcast fallback if
unmapped). Also enable m_automaticImportPlatformFriends (Steam friends).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When GAMESERVER_ADDRESS ("host:port") is set, EnterMatchmaking returns
that server (SingleplayerStation=false) and EnterMatchmakingMatch's
deploy signal carries its address, so the client travels to the real
game server instead of the client-hosted station. Unset = unchanged
(normal preprod). Lets a 2nd preprod run the same :preprod image and
differ only by this env var (dedicated-server test bench).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Symptom: missions done in raid zones weren't saved -> contracts stuck on
'Objective not met', no claim possible.

Root cause: client-hosted raids have no dedicated game server, so
UpdatePlayerActiveContracts is never called (confirmed: 0 calls in prod logs).
Only Kills and OwnNumOfItem objectives were handled; DeadDrop/VisitArea/
LootContainer had Progress[i]=0 forever -> unclaimable and not shown at station.

Fix: EYContractObjectiveType.IsRaidRuntime() groups the objectives that can only
be observed in a live raid (Kills/DeadDrop/VisitArea/LootContainer). Auto-credit
them on deploy (EnterMatchmakingMatch, persisted) so the station shows them
complete, and accept them in ClaimActiveContract. OwnNumOfItem still validated
against the real stash; FactionLevel/CompletedMission unchanged. Added a
defensive guard against a Progress array shorter than the objective list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regression from the contract-lock feature: the check gated on any non-empty
ContractLockPurchase, but the blueprint data uses the string "None" (not "")
for items with no lock (749/773 items). So virtually every item returned
'Item is locked' before the currency deduction -> players couldn't buy anything
(error shown, K-Marks unchanged). Skip the gate when the value is null/empty/"None".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit of the emulator surface + 4 fixes:
- Fortuna pass level was frozen: nothing ever granted season XP. ClaimActiveContract
  now grants + persists FortunaPass{2,3}_SeasonXp (scaled to the contract's reputation)
  and returns the new total, so the pass level moves as you complete contracts.
- UpdateUserTitleDisplayName never persisted the rename (echoed back, lost on relog).
  Added DbUserService.UpdateDisplayNameAsync and call it.
- GetPlayerSets returned a blank loadout; now reads the persisted LOADOUT key.
- GetCraftingInProgressData was a stub; now returns the persisted CraftingTimer so an
  in-progress craft + remaining time survive a menu reopen/relog.

Remaining known gaps documented in Plane (need client data / multiplayer / out of scope).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(audit léger): stats carrière, présence amis, AddGenericId
Build & Deploy / build (push) Successful in 43s
0a68a97291
- GetPlayerStatisticsRequestsClient: renvoyait vide -> renvoie les stats demandées
  (0 pour l'instant, raid client-hosted non traçable) pour que l'écran carrière s'affiche.
- UpdatePlayerPresenceState: persiste {inMatch,lastSeenUtc}; GetFriendList lit la présence
  et calcule onlineState (online si vu <3min, 2=en raid) au lieu de 0 en dur.
- AddGenericId: persiste l'id de service lié (était un no-op).
(Free-loadout non touché: S3 only, pas de free-loadout en S2.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(admin): read-only back-office at /admin (LAN-only)
Build & Deploy / build (push) Successful in 38s
b8d023b02a
Filament-style dark UI served directly by the API:
- Dashboard: player/entity/userdata/titledata counts + recent players
- /admin/players: searchable player list (name/id/steam)
- /admin/players/{id}: full player view — balance/faction/Fortuna cards, inventory
  table, and every UserData key pretty-printed (collapsible)
- /admin/titledata + /admin/titledata/{key}: browse in-memory title data
Restricted to LAN/loopback callers (RemoteIpAddress check) so it is never reachable
from the public internet even though :8443 is port-forwarded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Accounts were created as 'Unknown' and the client never sends a name, so the admin
panel / friends / squad showed no names. Add SteamWebApiService (GetPlayerSummaries),
wired into LoginWithSteam to backfill the DisplayName from the Steam persona when it's
still 'Unknown'. Admin gets a LAN-only /admin/backfill-names action (+ button) to
backfill existing accounts in one shot. Key read from config SteamWebApiKey (Vault ->
env), inert when unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bundle a compact id -> {name,rarity,category} lookup (Data/gameref.json, 1177 entries,
built from community-datamined TCF-Wiki data) and a GameRefService. The admin back-office
now shows readable names + coloured rarity for inventory items instead of raw ids, and a
new /admin/catalog page browses the known game catalog (searchable, by category).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Players and Catalog pages now fetch /admin/api/{players,catalog} JSON and render
a self-contained vanilla-JS data table: clickable column sort, live search,
dropdown filters (auth / category / rarity), page-size selector + prev/next
paging, result count. No CDN, works offline. Server-rendered dashboard,
player detail and titledata pages unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DT table engine lived in the page footer, after the per-page
<script>DT.init(...)</script> in the body, so DT was undefined when init ran
(ReferenceError, nothing rendered). Move the engine into a DtScript const
injected right after <main>, before any body content, so it is always defined
first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs: rewrite README around our build/CI + client switch tool
Build & Deploy / build (push) Successful in 32s
95541b6221
Replace the upstream Windows/local-run README with a dev-focused one: how the
client redirection works (loader+agent+backend.txt), repo structure, building
and running the API in a container, the TLS cert SAN gotcha, the Gitea CI/CD
branch model (preprod:preprod / main:latest), the Prospect.Client.Config
switch+cert tool, and an up-to-date feature status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The claim function only grants a tier when a FortunaPass2_Rewards TitleData catalog
maps its rewardId (Level_N, sent by the client) to an item/amount. That catalog was
absent (client-side DataTable) so claims granted nothing and stayed unclaimable.
Add a Level_1..Level_100 catalog (K-Marks scaling, Insurance every 5, Aurum every 10)
so players can actually collect Fortuna pass rewards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The friend tile rendered empty/offline because the UE client couldn't read our
best-guess response shape. Expose each friend's identity/name/steam/presence under
every plausible field name (camelCase + PlayFab PascalCase, flat + nested profile)
under both friends/Friends keys, and log the JSON so we can pin the exact shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
neckfire merged commit f7048ff173 into game-server 2026-07-16 08:25:21 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neckfire/the-cycle#16