mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:40:44 +00:00
spawned
This commit is contained in:
@@ -106,6 +106,7 @@ export function buildAnalytics(
|
||||
archiveDurationSec: lobby.archiveDurationSec,
|
||||
archiveConnectedPlayers: lobby.archiveConnectedPlayers,
|
||||
archiveActivePlayers: lobby.archiveActivePlayers,
|
||||
archiveSpawnedPlayers: lobby.archiveSpawnedPlayers,
|
||||
scheduledStartAt: lobby.scheduledStartAt,
|
||||
peakClients: lobby.peakClients,
|
||||
maxPlayers: lobby.maxPlayers,
|
||||
|
||||
@@ -38,6 +38,7 @@ function isBuildIntentType(type: string): boolean {
|
||||
function deriveReplayParticipationMetrics(archivePayload: unknown): {
|
||||
connectedPlayers?: number;
|
||||
activePlayers?: number;
|
||||
spawnedPlayers?: number;
|
||||
} {
|
||||
const root = asRecord(archivePayload);
|
||||
if (!root) return {};
|
||||
@@ -47,6 +48,7 @@ function deriveReplayParticipationMetrics(archivePayload: unknown): {
|
||||
|
||||
const connectedClientIds = new Set<string>();
|
||||
const activeClientIds = new Set<string>();
|
||||
const spawnedClientIds = new Set<string>();
|
||||
let sawDisconnectedMarker = false;
|
||||
|
||||
for (const turnEntry of turns) {
|
||||
@@ -74,12 +76,16 @@ function deriveReplayParticipationMetrics(archivePayload: unknown): {
|
||||
if (isBuildIntentType(type)) {
|
||||
activeClientIds.add(clientID);
|
||||
}
|
||||
if (type === "spawn") {
|
||||
spawnedClientIds.add(clientID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
connectedPlayers: sawDisconnectedMarker ? connectedClientIds.size : undefined,
|
||||
activePlayers: activeClientIds.size,
|
||||
spawnedPlayers: spawnedClientIds.size,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -418,6 +424,7 @@ export class LobbyIngestService {
|
||||
(record) =>
|
||||
record.status !== "active" &&
|
||||
(!record.archiveFound ||
|
||||
record.archiveSpawnedPlayers === undefined ||
|
||||
(record.archiveConnectedPlayers === undefined &&
|
||||
record.archiveActivePlayers === undefined)) &&
|
||||
!!record.closedAt &&
|
||||
@@ -579,6 +586,7 @@ export class LobbyIngestService {
|
||||
players: info?.players?.length,
|
||||
connectedPlayers: replayMetrics.connectedPlayers,
|
||||
activePlayers: replayMetrics.activePlayers,
|
||||
spawnedPlayers: replayMetrics.spawnedPlayers,
|
||||
durationSec:
|
||||
typeof info?.duration === "number" ? Math.round(info.duration) : undefined,
|
||||
winner: winnerLabel,
|
||||
|
||||
@@ -327,6 +327,7 @@ export class JsonStore {
|
||||
players?: number;
|
||||
connectedPlayers?: number;
|
||||
activePlayers?: number;
|
||||
spawnedPlayers?: number;
|
||||
durationSec?: number;
|
||||
winner?: string;
|
||||
lobbyCreatedAt?: number;
|
||||
@@ -340,6 +341,7 @@ export class JsonStore {
|
||||
lobby.archivePlayers = payload.players;
|
||||
lobby.archiveConnectedPlayers = payload.connectedPlayers;
|
||||
lobby.archiveActivePlayers = payload.activePlayers;
|
||||
lobby.archiveSpawnedPlayers = payload.spawnedPlayers;
|
||||
lobby.archiveDurationSec = payload.durationSec;
|
||||
lobby.archiveWinner = payload.winner;
|
||||
lobby.actualLobbyCreatedAt = payload.lobbyCreatedAt;
|
||||
|
||||
+15
-2
@@ -93,6 +93,7 @@ export interface LobbyRecord {
|
||||
archivePlayers?: number;
|
||||
archiveConnectedPlayers?: number;
|
||||
archiveActivePlayers?: number;
|
||||
archiveSpawnedPlayers?: number;
|
||||
archiveDurationSec?: number;
|
||||
archiveWinner?: string;
|
||||
actualLobbyCreatedAt?: number;
|
||||
@@ -166,6 +167,7 @@ export interface AnalyticsPayload {
|
||||
archiveDurationSec?: number;
|
||||
archiveConnectedPlayers?: number;
|
||||
archiveActivePlayers?: number;
|
||||
archiveSpawnedPlayers?: number;
|
||||
scheduledStartAt: number;
|
||||
peakClients: number;
|
||||
maxPlayers?: number;
|
||||
@@ -206,8 +208,19 @@ export function safeMaxPlayers(record: Pick<LobbyRecord, "maxPlayers">): number
|
||||
return Math.max(1, record.maxPlayers ?? 1);
|
||||
}
|
||||
|
||||
export function peakFillRatio(record: Pick<LobbyRecord, "peakClients" | "maxPlayers">): number {
|
||||
return record.peakClients / safeMaxPlayers(record as Pick<LobbyRecord, "maxPlayers">);
|
||||
export function peakFillClients(
|
||||
record: Pick<LobbyRecord, "peakClients" | "archiveConnectedPlayers" | "archivePlayers">,
|
||||
): number {
|
||||
return record.archiveConnectedPlayers ?? record.archivePlayers ?? record.peakClients;
|
||||
}
|
||||
|
||||
export function peakFillRatio(
|
||||
record: Pick<
|
||||
LobbyRecord,
|
||||
"peakClients" | "maxPlayers" | "archiveConnectedPlayers" | "archivePlayers"
|
||||
>,
|
||||
): number {
|
||||
return peakFillClients(record) / safeMaxPlayers(record as Pick<LobbyRecord, "maxPlayers">);
|
||||
}
|
||||
|
||||
export function bucketForConfig(
|
||||
|
||||
+26
-8
@@ -3,6 +3,7 @@ import {
|
||||
BucketMode,
|
||||
LobbyRecord,
|
||||
TimelineBucket,
|
||||
peakFillClients,
|
||||
} from "../shared/types";
|
||||
import * as d3 from "d3";
|
||||
import "./styles.css";
|
||||
@@ -617,7 +618,7 @@ function renderOrder(payload: AnalyticsPayload): void {
|
||||
<th>Status</th>
|
||||
<th>Lobby + Game</th>
|
||||
<th>Peak Fill</th>
|
||||
<th>Connected / Active</th>
|
||||
<th>Connected / Active / Spawned</th>
|
||||
<th>Join/min</th>
|
||||
<th>Opened</th>
|
||||
</tr>
|
||||
@@ -635,7 +636,7 @@ function renderOrder(payload: AnalyticsPayload): void {
|
||||
</td>
|
||||
<td class="status-${row.status}">${row.status}</td>
|
||||
<td>${formatDurationMs(row.openDurationMs)} + ${formatGameDuration(row, payload.now)}</td>
|
||||
<td>${row.maxPlayers ? `${row.peakClients}/${row.maxPlayers}` : row.peakClients}</td>
|
||||
<td>${formatPeakFill(row)}</td>
|
||||
<td>${formatReplayParticipation(row)}</td>
|
||||
<td>${row.joinRatePerMin.toFixed(2)}</td>
|
||||
<td>${new Date(row.openedAt).toLocaleString()}</td>
|
||||
@@ -684,7 +685,7 @@ function renderInteresting(target: "neverStarted" | "lowFill", rows: LobbyRecord
|
||||
<td class="mono">${row.gameID}</td>
|
||||
<td>${row.gameConfig?.gameMode ?? "-"}</td>
|
||||
<td>${row.gameConfig?.gameMap ?? "-"}</td>
|
||||
<td>${row.maxPlayers ? `${row.peakClients}/${row.maxPlayers}` : row.peakClients}</td>
|
||||
<td>${formatPeakFill(row)}</td>
|
||||
<td>${row.fillRatioAtStart !== undefined ? `${(row.fillRatioAtStart * 100).toFixed(1)}%` : "-"}</td>
|
||||
<td>${formatDurationMs(row.openDurationMs)}</td>
|
||||
</tr>
|
||||
@@ -723,20 +724,37 @@ function formatDurationSec(durationSec: number | undefined): string {
|
||||
function formatReplayParticipation(
|
||||
row: Pick<
|
||||
AnalyticsPayload["order"][number],
|
||||
"archivePlayers" | "archiveConnectedPlayers" | "archiveActivePlayers"
|
||||
| "archivePlayers"
|
||||
| "archiveConnectedPlayers"
|
||||
| "archiveActivePlayers"
|
||||
| "archiveSpawnedPlayers"
|
||||
>,
|
||||
): string {
|
||||
const connected = row.archiveConnectedPlayers;
|
||||
const active = row.archiveActivePlayers;
|
||||
const spawned = row.archiveSpawnedPlayers;
|
||||
const total = row.archivePlayers;
|
||||
|
||||
if (connected === undefined && active === undefined) {
|
||||
if (connected === undefined && active === undefined && spawned === undefined) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const pair = `${connected ?? "-"} / ${active ?? "-"}`;
|
||||
if (total === undefined) return pair;
|
||||
return `${pair} of ${total}`;
|
||||
const triplet = `${connected ?? "-"} / ${active ?? "-"} / ${spawned ?? "-"}`;
|
||||
if (total === undefined) return triplet;
|
||||
return `${triplet} of ${total}`;
|
||||
}
|
||||
|
||||
function formatPeakFill(
|
||||
row: Pick<
|
||||
AnalyticsPayload["order"][number],
|
||||
"peakClients" | "maxPlayers" | "archiveConnectedPlayers" | "archivePlayers"
|
||||
>,
|
||||
): string {
|
||||
const peak = peakFillClients(row);
|
||||
if (row.maxPlayers && row.maxPlayers > 0) {
|
||||
return `${peak}/${row.maxPlayers}`;
|
||||
}
|
||||
return String(peak);
|
||||
}
|
||||
|
||||
function formatGameDuration(
|
||||
|
||||
Reference in New Issue
Block a user