mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
lobby browser: spectator column, players online, gallery latency
- lobby_list rows carry allow_spectators / max_spectators / spectator_count (browser shows "No" or "1/4"), and every lobby_list carries a players array: everyone on the hub with country, room and a short connection tag so a client can pick out its own row without publishing whole ids. - Spectator latency: the round-trip table was keyed by seat and bounds- checked against its size, so gallery seats (numbered from 64) were always dropped. Seats now map to table cells, player seats first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1dT6h1ZshMVUUVKTPxsCD
This commit is contained in:
@@ -51,8 +51,26 @@ typedef struct PsxLobbyRow {
|
||||
int latency_ms;
|
||||
/* Host's country (alpha-2) from the server's GeoIP; "" unknown. */
|
||||
char host_country[4];
|
||||
/* Gallery: 0 = none; else how many watch out of how many seats. */
|
||||
int allow_spectators;
|
||||
int max_spectators;
|
||||
int spectator_count;
|
||||
} PsxLobbyRow;
|
||||
|
||||
/* One player connected to the hub (the lobby_list `players` array). */
|
||||
typedef struct PsxLobbyOnlinePlayer {
|
||||
char player_id[PSX_LOBBY_ID_LEN];
|
||||
char display_name[PSX_LOBBY_NAME_LEN];
|
||||
char country[4];
|
||||
char lobby_id[PSX_LOBBY_ID_LEN];
|
||||
char lobby_name[PSX_LOBBY_NAME_LEN];
|
||||
int hosting;
|
||||
/* First 8 characters of the player's connection id: enough to tell
|
||||
* "which row is me" without publishing whole ids to browsers. */
|
||||
char tag[12];
|
||||
} PsxLobbyOnlinePlayer;
|
||||
#define PSX_LOBBY_MAX_ONLINE 64
|
||||
|
||||
typedef struct PsxLobbyMember {
|
||||
/* Seat index in the shared namespace: a player seat, or
|
||||
* spectator_slot_base + gallery index. Pass it back to kick / move as-is. */
|
||||
@@ -219,6 +237,9 @@ void psx_lobby_set_max_slots(int max_slots);
|
||||
void psx_lobby_request_list(void);
|
||||
int psx_lobby_list_count(void);
|
||||
int psx_lobby_list_get(int index, PsxLobbyRow *out);
|
||||
/* Players connected to the hub, refreshed with every lobby_list. */
|
||||
int psx_lobby_online_count(void);
|
||||
int psx_lobby_online_get(int index, PsxLobbyOnlinePlayer *out);
|
||||
|
||||
/*
|
||||
* Create lobby. host_bind e.g. "0.0.0.0:7777". password may be NULL/empty.
|
||||
|
||||
@@ -10831,6 +10831,32 @@ namespace {
|
||||
out->latency_ms = row.latency_ms;
|
||||
std::snprintf(out->host_country, sizeof(out->host_country), "%s",
|
||||
row.host_country);
|
||||
out->allow_spectators = row.allow_spectators;
|
||||
out->max_spectators = row.max_spectators;
|
||||
out->spectator_count = row.spectator_count;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Players online: the hub's `players` list. LAN rooms have no hub, so
|
||||
* a LAN-only session reports nobody rather than guessing. */
|
||||
int ae_np_online_count(void*) {
|
||||
return psx_lobby_connected() ? psx_lobby_online_count() : 0;
|
||||
}
|
||||
int ae_np_online_get(void*, int index, RecompLauncherCNetplayOnlinePlayer* out) {
|
||||
if (!out) return 0;
|
||||
PsxLobbyOnlinePlayer p{};
|
||||
if (!psx_lobby_online_get(index, &p)) return 0;
|
||||
std::memset(out, 0, sizeof(*out));
|
||||
std::snprintf(out->display_name, sizeof(out->display_name), "%s", p.display_name);
|
||||
std::snprintf(out->country, sizeof(out->country), "%s", p.country);
|
||||
std::snprintf(out->lobby_name, sizeof(out->lobby_name), "%s", p.lobby_name);
|
||||
out->in_lobby = p.lobby_id[0] != '\0';
|
||||
out->hosting = p.hosting;
|
||||
/* Which row is us: the hub tags each row with the first characters
|
||||
* of its connection id. A name match would mark every namesake. */
|
||||
const char* me = psx_lobby_player_id();
|
||||
out->is_local = me && me[0] && p.tag[0] &&
|
||||
std::strncmp(me, p.tag, std::strlen(p.tag)) == 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -11986,6 +12012,8 @@ namespace {
|
||||
g_lnch_netplay_callbacks.chat_count = ae_np_chat_count;
|
||||
g_lnch_netplay_callbacks.chat_get = ae_np_chat_get;
|
||||
g_lnch_netplay_callbacks.host_can_spectate = ae_np_host_can_spectate;
|
||||
g_lnch_netplay_callbacks.online_count = ae_np_online_count;
|
||||
g_lnch_netplay_callbacks.online_get = ae_np_online_get;
|
||||
g_lnch_netplay_callbacks.seat_move_self = ae_np_seat_move_self;
|
||||
g_lnch_netplay_callbacks.seat_swap_request = ae_np_seat_swap_request;
|
||||
g_lnch_netplay_callbacks.seat_swap_incoming = ae_np_seat_swap_incoming;
|
||||
|
||||
@@ -197,6 +197,8 @@ typedef struct {
|
||||
size_t ws_pending_len;
|
||||
PsxLobbyRow list[PSX_LOBBY_MAX_LIST];
|
||||
int list_count;
|
||||
PsxLobbyOnlinePlayer online[PSX_LOBBY_MAX_ONLINE];
|
||||
int online_count;
|
||||
int in_lobby;
|
||||
int is_host;
|
||||
char host_player_id[PSX_LOBBY_ID_LEN];
|
||||
@@ -916,6 +918,22 @@ static void member_rtt_clear(void)
|
||||
g_lc.rtt_next_ping_ms = 0;
|
||||
}
|
||||
|
||||
/* member_rtt_ms has a cell per seat in BOTH tables, but a gallery seat is
|
||||
* numbered from the server's spectator base (64+), not from the array. Map
|
||||
* a seat to its cell; -1 for a seat neither table has. Without this every
|
||||
* spectator's report was dropped as out of range and the gallery showed no
|
||||
* latency at all. */
|
||||
static int rtt_index_for_slot(int slot)
|
||||
{
|
||||
int base;
|
||||
if (slot < 0) return -1;
|
||||
if (slot < PSX_LOBBY_MAX_PLAYERS) return slot;
|
||||
base = psx_lobby_spectator_slot_base();
|
||||
if (base > 0 && slot >= base && slot < base + PSX_LOBBY_MAX_SPECTATORS)
|
||||
return PSX_LOBBY_MAX_PLAYERS + (slot - base);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int member_slot_for_player(const char *player_id)
|
||||
{
|
||||
int i;
|
||||
@@ -1259,6 +1277,66 @@ static int json_get_int(const char *json, const char *key, int def)
|
||||
return (int)strtol(p + 1, NULL, 10);
|
||||
}
|
||||
|
||||
static int json_get_bool(const char *json, const char *key, int def);
|
||||
|
||||
/* The `players` array of a lobby_list: everyone on the hub. Flat objects,
|
||||
* so each one is cut out by brace depth and read with the key getters. An
|
||||
* older server has no such array and the count simply goes to zero. */
|
||||
static void lobby_list_parse_players(const char *json)
|
||||
{
|
||||
const char *p = strstr(json, "\"players\"");
|
||||
int n = 0;
|
||||
g_lc.online_count = 0;
|
||||
if (!p) return;
|
||||
p = strchr(p, '[');
|
||||
if (!p) return;
|
||||
++p;
|
||||
while (*p && n < PSX_LOBBY_MAX_ONLINE) {
|
||||
const char *obj, *end;
|
||||
int depth = 0;
|
||||
char chunk[512];
|
||||
size_t len;
|
||||
while (*p && *p != '{' && *p != ']') ++p;
|
||||
if (*p != '{') break;
|
||||
obj = end = p;
|
||||
do {
|
||||
if (*end == '{') ++depth;
|
||||
else if (*end == '}') --depth;
|
||||
++end;
|
||||
} while (*end && depth > 0);
|
||||
len = (size_t)(end - obj);
|
||||
if (len >= sizeof(chunk)) len = sizeof(chunk) - 1;
|
||||
memcpy(chunk, obj, len);
|
||||
chunk[len] = '\0';
|
||||
memset(&g_lc.online[n], 0, sizeof(g_lc.online[n]));
|
||||
json_get_str(chunk, "display_name", g_lc.online[n].display_name,
|
||||
sizeof(g_lc.online[n].display_name));
|
||||
json_get_str(chunk, "country", g_lc.online[n].country,
|
||||
sizeof(g_lc.online[n].country));
|
||||
json_get_str(chunk, "lobby_id", g_lc.online[n].lobby_id,
|
||||
sizeof(g_lc.online[n].lobby_id));
|
||||
json_get_str(chunk, "lobby_name", g_lc.online[n].lobby_name,
|
||||
sizeof(g_lc.online[n].lobby_name));
|
||||
g_lc.online[n].hosting = json_get_bool(chunk, "hosting", 0);
|
||||
json_get_str(chunk, "tag", g_lc.online[n].tag, sizeof(g_lc.online[n].tag));
|
||||
if (g_lc.online[n].display_name[0]) ++n;
|
||||
p = end;
|
||||
}
|
||||
g_lc.online_count = n;
|
||||
}
|
||||
|
||||
int psx_lobby_online_count(void)
|
||||
{
|
||||
return g_lc.online_count;
|
||||
}
|
||||
|
||||
int psx_lobby_online_get(int index, PsxLobbyOnlinePlayer *out)
|
||||
{
|
||||
if (!out || index < 0 || index >= g_lc.online_count) return 0;
|
||||
*out = g_lc.online[index];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int json_get_bool(const char *json, const char *key, int def)
|
||||
{
|
||||
char pat[80];
|
||||
@@ -1783,6 +1861,7 @@ static void handle_server_json(const char *json)
|
||||
if (strcmp(op, "lobby_list") == 0) {
|
||||
const char *p = strstr(json, "\"lobbies\"");
|
||||
int n = 0;
|
||||
lobby_list_parse_players(json);
|
||||
/* Keep prior RTTs across server list pushes; Refresh re-probes.
|
||||
* Invalidate when host_endpoint or lan_endpoints change. */
|
||||
char prev_ids[PSX_LOBBY_MAX_LIST][PSX_LOBBY_ID_LEN];
|
||||
@@ -1885,6 +1964,9 @@ static void handle_server_json(const char *json)
|
||||
g_lc.list[n].has_password = json_get_bool(chunk, "has_password", 0);
|
||||
json_get_str(chunk, "host_country", g_lc.list[n].host_country,
|
||||
sizeof(g_lc.list[n].host_country));
|
||||
g_lc.list[n].allow_spectators = json_get_bool(chunk, "allow_spectators", 0);
|
||||
g_lc.list[n].max_spectators = json_get_int(chunk, "max_spectators", 0);
|
||||
g_lc.list[n].spectator_count = json_get_int(chunk, "spectator_count", 0);
|
||||
json_get_str(chunk, "host_endpoint", g_lc.list[n].host_endpoint,
|
||||
sizeof(g_lc.list[n].host_endpoint));
|
||||
g_lc.list[n].lan_count = json_parse_str_array(
|
||||
@@ -2142,12 +2224,11 @@ static void handle_server_json(const char *json)
|
||||
if (type == PSX_LOBBY_SIG_RTT_PING || type == PSX_LOBBY_SIG_RTT_PONG)
|
||||
return;
|
||||
if (type == PSX_LOBBY_SIG_RTT_REPORT) {
|
||||
int slot = member_slot_for_player(from);
|
||||
int slot = rtt_index_for_slot(member_slot_for_player(from));
|
||||
int ms = (int)strtol(text_buf, NULL, 10);
|
||||
/* Max with local measure — never let an optimistic peer REPORT
|
||||
* undercut delay provisioning (Force TURN / asymmetric ICE). */
|
||||
if (slot >= 0 && slot < PSX_LOBBY_MAX_MEMBERS && ms >= 0 &&
|
||||
ms <= 60000) {
|
||||
if (slot >= 0 && ms >= 0 && ms <= 60000) {
|
||||
if (g_lc.member_rtt_ms[slot] < 0 ||
|
||||
ms > g_lc.member_rtt_ms[slot])
|
||||
g_lc.member_rtt_ms[slot] = ms;
|
||||
@@ -2743,8 +2824,8 @@ static void lobby_rtt_store_for_peer(const char *peer_id, int ms)
|
||||
int slot;
|
||||
if (ms < 0 || !peer_id || !peer_id[0])
|
||||
return;
|
||||
slot = member_slot_for_player(peer_id);
|
||||
if (slot < 0 || slot >= PSX_LOBBY_MAX_MEMBERS)
|
||||
slot = rtt_index_for_slot(member_slot_for_player(peer_id));
|
||||
if (slot < 0)
|
||||
return;
|
||||
if (g_lc.member_rtt_ms[slot] < 0 || ms > g_lc.member_rtt_ms[slot])
|
||||
g_lc.member_rtt_ms[slot] = ms;
|
||||
@@ -3350,13 +3431,14 @@ int psx_lobby_member_get(int index, PsxLobbyMember *out)
|
||||
|
||||
int psx_lobby_member_latency_ms(int slot)
|
||||
{
|
||||
int local;
|
||||
if (slot < 0 || slot >= PSX_LOBBY_MAX_MEMBERS)
|
||||
int local, idx;
|
||||
idx = rtt_index_for_slot(slot);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
local = local_member_slot();
|
||||
if (local >= 0 && slot == local)
|
||||
return -1; /* never show self-RTT */
|
||||
return g_lc.member_rtt_ms[slot];
|
||||
return g_lc.member_rtt_ms[idx];
|
||||
}
|
||||
|
||||
int psx_lobby_member_is_host(const PsxLobbyMember *member)
|
||||
|
||||
Reference in New Issue
Block a user