Files
tws-zombies-scripts/t6/tws_t6_telemetry.gsc
T

164 lines
5.4 KiB
Plaintext

/* tws_t6_telemetry.gsc - direct HTTP round telemetry via t6-gsc-utils. */
#include maps\mp\_utility;
#include common_scripts\utility;
// Plutonium probes both entry points; runtime setup belongs in init.
main()
{
println("[tws_t6_telemetry] main reached (HTTP API target: t6-gsc-utils v3.1.1)");
}
init()
{
println("[tws_t6_telemetry] init reached");
// Loaded only from scripts/zm; no shared HUD configuration is needed.
// Enabled when unset; explicitly set 0 to opt out.
if (GetDvar("tws_telemetry_enabled") == "0")
{
println("[tws_t6_telemetry] disabled");
return;
}
// Repeated init in the same VM must not create competing senders.
if (isDefined(level.telemetry_initialized))
return;
level.telemetry_url = GetDvar("tws_telemetry_url");
if (level.telemetry_url == "")
level.telemetry_url = "http://192.168.1.199:3000/api/telemetry";
level.telemetry_server = GetDvar("tws_server_id");
if (level.telemetry_server == "" || level.telemetry_server.size > 255)
{
println("[tws_t6_telemetry] Set tws_server_id to the exact registered server name (1-255 characters)");
return;
}
level.telemetry_initialized = true;
level.telemetry_pending = undefined;
println("[tws_t6_telemetry] watching for an occupied positive round");
level thread telemetry_delivery_worker();
level thread telemetry_round_watcher();
}
// Independent of HUD installation. Report the first occupied positive round,
// including round 1 when the map pre-seeds it, then each observed transition.
telemetry_round_watcher()
{
level endon("end_game");
level endon("game_ended");
level endon("intermission");
last_round = -1;
for (;;)
{
if (isDefined(level.round_number) && level.round_number >= 1 &&
level.round_number != last_round)
{
players = getPlayers();
if (players.size > 0)
{
last_round = level.round_number;
// Snapshot values before handing off to the HTTP thread.
data = [];
data["event"] = "round_change";
data["round"] = int(last_round);
data["server"] = level.telemetry_server;
data["players"] = players.size;
// No runId field (including null/empty), boot, session or
// feature events in Fleet-only mode. Keep just the newest
// pending observation so an outage cannot build a backlog.
pending = spawnStruct();
println("[tws_t6_telemetry] serializing round " + last_round);
pending.body = jsonSerialize(data);
pending.observed_at = getTime();
level.telemetry_pending = pending;
}
}
wait (0.5);
}
}
telemetry_delivery_worker()
{
level endon("end_game");
level endon("game_ended");
level endon("intermission");
backoff = [];
backoff[0] = 1;
backoff[1] = 2;
backoff[2] = 5;
for (;;)
{
if (!isDefined(level.telemetry_pending))
{
wait (0.5);
continue;
}
pending = level.telemetry_pending;
level.telemetry_pending = undefined;
// One in-flight request plus one replaceable pending observation.
// A newer observation always retires retries of an older round.
for (attempt = 0; attempt < 4; attempt++)
{
if (isDefined(level.telemetry_pending) ||
getTime() - pending.observed_at > 15000)
break;
if (telemetry_send(pending.body))
break;
if (attempt < 3)
wait (backoff[attempt]);
}
}
}
// Returns transport completion only, NEVER beta/API acceptance. The released
// plugin exposes body/success/error but no HTTP status or cancellation. Do not
// start a replacement request on a script timeout: the old one could still
// commit later and overwrite Fleet with a stale round. Gameplay never waits
// on this worker. Malformed/HTML bodies are not passed to throwing jsonParse.
telemetry_send(body)
{
headers = [];
headers["Content-Type"] = "application/json";
options = [];
options["method"] = "POST";
options["headers"] = headers;
options["body"] = body;
// v3.1.1 global alias: options array, NOT positional body/headers.
// Avoid the raw loader treating the http namespace as http.gsc.
println("[tws_t6_telemetry] calling httpPost");
request = httpPost(level.telemetry_url, options);
println("[tws_t6_telemetry] HTTP handle returned; waiting for completion");
request waittill("done", response, success, error);
println("[tws_t6_telemetry] HTTP completion received");
// The plugin's success flag reports transport completion, not HTTP status.
// Unscoped round reports replace Fleet state; retrying the same snapshot
// cannot duplicate beta evidence. HTTP errors cannot be classified here,
// so only reported transport failures receive bounded retries.
if (!isDefined(success) || !success)
{
if (!isDefined(error))
error = "unknown transport error";
print("^1[tws_t6_telemetry] Round telemetry failed: " + error);
return false;
}
println("[tws_t6_telemetry] Transport completed; API acceptance is not verified by this plugin");
return true;
}