Files

153 lines
4.5 KiB
Plaintext

/* tws_t6_join.gsc - .join lets a newcomer leave spectator mode before round end. */
#include maps\mp\_utility;
#include common_scripts\utility;
// Plutonium probes both entry points; runtime setup belongs in init.
main()
{
println("[tws_t6_join] main reached");
}
init()
{
println("[tws_t6_join] init reached");
scripts\zm\tws_t6_common::ensure_config();
scripts\zm\tws_t6_common::register_help_command(".join", "Join mid-round (newcomers only)");
scripts\zm\tws_t6_common::register_spawn_handler(::join_spawn);
level thread join_chat_listener();
}
// T6 first emits spawned_player during setup, then moves late connectors
// into spectator mode after 0.05 seconds. Observe the settled state instead.
// The common spawn dispatcher runs only once, so keep listening ourselves.
join_spawn()
{
self endon("disconnect");
level endon("end_game");
level endon("game_ended");
level endon("intermission");
self thread join_track_death();
for (;;)
{
self.tws_join_spawn_pending = true;
wait 0.1;
if (isDefined(self.sessionstate) && self.sessionstate == "playing")
self.tws_join_has_played = true;
self.tws_join_spawn_pending = false;
if (is_true(self.tws_join_has_played))
return;
self waittill("spawned_player");
}
}
// A real death during the settling window still consumes newcomer eligibility.
// Never clear this flag on death, a round change, or a subsequent spawn.
join_track_death()
{
self endon("disconnect");
level endon("end_game");
level endon("game_ended");
level endon("intermission");
self waittill("killed_player");
self.tws_join_has_played = true;
}
join_chat_listener()
{
level endon("end_game");
level endon("game_ended");
level endon("intermission");
for (;;)
{
level waittill("say", message, player);
if (!isDefined(player) || !isDefined(message))
continue;
if (toLower(message) == ".join")
player thread join_request();
}
}
join_request()
{
self endon("disconnect");
level endon("end_game");
level endon("game_ended");
level endon("intermission");
// Setup spawns do not consume eligibility; actual play and .join do.
if (is_true(self.tws_join_has_played) || is_true(self.tws_join_used))
{
self iPrintLnBold("^3.join is only for newcomers who have not played yet");
return;
}
// Fail closed while the stock connection setup is still in progress.
// T6 stock does not populate self.initialized the way T5 SP zombiemode
// does; use sessionstate as the readiness signal.
if (is_true(self.tws_join_spawn_pending) ||
!isDefined(self.sessionstate) || self.sessionstate != "spectator")
{
self iPrintLnBold("^3.join is only available while waiting to enter the game");
return;
}
if (is_true(level.intermission) || is_true(level.gameended) ||
!isDefined(level.round_number) || level.round_number < 1 ||
!isDefined(level.spawnplayer) || !join_has_active_player())
{
self iPrintLnBold("^3The game is not ready for .join");
return;
}
// Late connections can miss the stock seeding of self.spectator_respawn
// (stock asserts it in spectator_respawn); pick a random map spawn point
// as a fallback so the stock callback can proceed.
if (!isDefined(self.spectator_respawn))
{
points = getstructarray("initial_spawn_points", "targetname");
if (!isDefined(points) || points.size == 0)
{
self iPrintLnBold("^3No spawn point available; wait for the next round");
return;
}
self.spectator_respawn = points[randomInt(points.size)];
}
// Match stock spectators_respawn setup. Preserve any map-specific
// spawn callback if another mod already installed one.
if (!isDefined(level.custom_spawnplayer))
level.custom_spawnplayer = maps\mp\zombies\_zm::spectator_respawn;
// Consume BEFORE entering stock code, which may yield. Repeated chat
// commands cannot start overlapping spawns or grant another life.
self.tws_join_used = true;
self [[level.spawnplayer]]();
}
join_has_active_player()
{
players = getPlayers();
for (i = 0; i < players.size; i++)
{
player = players[i];
if (isDefined(player.sessionstate) && player.sessionstate == "playing" &&
isAlive(player) && !(player maps\mp\zombies\_zm_laststand::player_is_in_laststand()))
return true;
}
return false;
}