Allow solo and last-active-player AFK

This commit is contained in:
2026-10-17 22:19:04 -04:00
parent c19bef6dc6
commit caaaa102a4
9 changed files with 115 additions and 142 deletions
+4 -1
View File
@@ -300,7 +300,10 @@ configuration, persistence, action results and deployment checks.
## Persistent AFK reserve
AFK `2.0.0` uses EC authorization and a local central countdown. See the
AFK `2.1.0` uses EC authorization and a local central countdown. See the
[shared AFK guide](../../docs/afk.md) for exact commands, dependencies, recovery
and live checks. Install plugin `1.4.0+` and fully restart the server process.
The countdown remains visible with `.hud` hidden; expiration has no grace period.
Solo and last-active-player entry are allowed, including all players being AFK
together; teammate loss does not force a match end. Upgrading from `2.0.0` adds
no dependencies and requires only copying the script and restarting the map.
+1 -60
View File
@@ -43,7 +43,6 @@ init()
level thread afk_startup();
level thread afk_end_watcher();
level thread afk_round_watcher();
level thread afk_team_watcher();
}
afk_startup()
@@ -405,11 +404,6 @@ afk_activate(player, state)
afk_say(state, "Wait briefly after returning before entering AFK again.");
return;
}
if (afk_other_active(player) == 0)
{
afk_say(state, "The last active player cannot enter AFK. Another player must remain in the fight.");
return;
}
data = afk_base(state, "reserve");
data["session_id"] = telemetryNewMatch();
data["seconds"] = 604800;
@@ -456,7 +450,7 @@ afk_activate(player, state)
reply = afk_http("start", start_body, start["request_id"]);
if (!isDefined(reply) || reply["state"] != "started" || reply["session_id"] != session ||
reply["authorized_seconds"] != authorized || !state.connected ||
state.cancelled || !afk_can_enter(player) || afk_other_active(player) == 0)
state.cancelled || !afk_can_enter(player))
{
afk_stop_state(state, "never_started");
afk_say(state, "AFK entry cancelled. No protected time used.");
@@ -707,59 +701,6 @@ afk_end_watcher()
level.afk_ended = true;
}
afk_other_active(excluded)
{
count = 0;
players = getPlayers();
for (i = 0; i < players.size; i++)
{
player = players[i];
if (isDefined(excluded) && player == excluded)
continue;
if (!afk_playing(player) || is_true(player.hud_afk_on))
continue;
if (isDefined(player.tws_afk_state) && player.tws_afk_state.pending)
continue;
count++;
}
return count;
}
afk_team_watcher()
{
level endon("end_game");
level endon("game_ended");
level endon("intermission");
for (;;)
{
players = getPlayers();
protected = 0;
survivors = 0;
stock_recovery = is_true(level.no_end_game_check) || is_true(level.isresetting_grief);
for (i = 0; i < players.size; i++)
{
player = players[i];
if (is_true(player.afterlife))
stock_recovery = true;
if (is_true(player.hud_afk_on))
protected++;
else if (afk_playing(player))
survivors++;
}
if (protected > 0 && survivors == 0 && !stock_recovery)
{
for (i = 0; i < players.size; i++)
if (is_true(players[i].hud_afk_on))
players[i] afk_finish("match_end", false);
// AFK does not change sessionstate/player count or zombie scaling.
// The verified stock end signal prevents AFK bodies prolonging play.
level notify("end_game");
return;
}
wait 0.05;
}
}
// Both stock engines notify the credited attacker, with the actual dead zombie.
// Never infer contributions from spendable score, score_total, or bank actions.
afk_kill_watcher()