/* tws_common.gsc — shared config, HUD helpers, connect/spawn/help registries. */ #include maps\_utility; #include common_scripts\utility; init() { if (GetDvar(#"zombiemode") != "1") return; common_config(); register_help_command(".help", "List available commands"); level thread help_chat_listener(); level thread on_player_connect_dispatch(); } common_config() { if (isDefined(level.hud_x)) return; // anchor: move the whole cluster with these two level.hud_x = 10; level.hud_y = 10; level.hud_row_match = level.hud_y; level.hud_row_round = level.hud_y + 15; level.hud_row_name = level.hud_y + 32; level.hud_row_count = level.hud_y + 54; level.hud_row_bar = level.hud_y + 84; level.hud_font_small = 1.4; level.hud_font_large = 2.0; level.hud_font_mid = 1.6; level.hud_bar_width = 120; level.hud_bar_height = 8; level.hud_col_count = level.hud_x + 90; level.hud_col_name = level.hud_x + 60; level.hud_col_health = level.hud_x + level.hud_bar_width + 12; level.hud_rate = 0.1; // green/amber/red ramp is health-only level.hud_c_label = (0.85, 0.85, 0.85); level.hud_c_dim = (0.55, 0.55, 0.55); level.hud_c_count = (1, 1, 1); level.hud_c_name = (0.4, 0.9, 0.5); level.hud_c_high = (0.2, 1, 0.4); level.hud_c_mid = (1, 0.8, 0); level.hud_c_low = (1, 0.2, 0.2); level.hud_c_afk = (1, 0.8, 0.2); // numbers fade in below this fraction of max hp level.hud_health_threshold = 1.0; if (!isDefined(level.tws_spawn_handlers)) level.tws_spawn_handlers = []; } ensure_config() { if (!isDefined(level.hud_x)) common_config(); } register_spawn_handler(fn) { if (!isDefined(level.tws_spawn_handlers)) level.tws_spawn_handlers = []; level.tws_spawn_handlers[level.tws_spawn_handlers.size] = fn; } // Connection handlers also serve newcomers still waiting in spectator mode. register_connect_handler(fn) { if (!isDefined(level.tws_connect_handlers)) level.tws_connect_handlers = []; level.tws_connect_handlers[level.tws_connect_handlers.size] = fn; } // Modules register their own entries so help only lists installed commands. register_help_command(command, description) { if (!isDefined(level.tws_help_commands)) level.tws_help_commands = []; entry = spawnStruct(); entry.command = command; entry.description = description; level.tws_help_commands[level.tws_help_commands.size] = entry; } // Listen at level scope so newcomers can ask for help before their first spawn. help_chat_listener() { for (;;) { level waittill("say", message, player); if (!isDefined(player) || !isDefined(message)) continue; if (toLower(message) == ".help") player show_help(); } } show_help() { // Cache the text per connection; repeated SetText calls consume config strings. if (!isDefined(self.tws_help_elems)) { elems = []; // Use the same anchors as the HUD, with room below the health numbers. y = level.hud_row_bar + level.hud_bar_height + 20; title = make_text_elem(self, level.hud_x, y, "left", level.hud_font_mid, level.hud_c_afk); title SetText("Commands"); elems[elems.size] = title; for (i = 0; i < level.tws_help_commands.size; i++) { entry = level.tws_help_commands[i]; elem = make_text_elem(self, level.hud_x, y + 22 + i * 18, "left", level.hud_font_small, level.hud_c_count); elem SetText("^3" + entry.command + "^7 - " + entry.description); elems[elems.size] = elem; } self.tws_help_elems = elems; self thread destroy_on_disconnect(elems); } for (i = 0; i < self.tws_help_elems.size; i++) { elem = self.tws_help_elems[i]; elem.alpha = 1; } // Only the expiry timer is replaced; the say listener and cleanup stay alive. self notify("tws_help_refresh"); self thread hide_help_after_delay(); } hide_help_after_delay() { self endon("disconnect"); self endon("tws_help_refresh"); wait 12; for (i = 0; i < self.tws_help_elems.size; i++) { elem = self.tws_help_elems[i]; elem.alpha = 0; } } // only connect listener in the fleet; a second one crashes the T5 SP handshake on_player_connect_dispatch() { for (;;) { level waittill("connected", player); player thread on_player_spawned_dispatch(); player thread on_player_connected_dispatch(); } } on_player_connected_dispatch() { self endon("disconnect"); // Leave the connection handshake before invoking optional modules. wait 0.05; if (!isDefined(level.tws_connect_handlers)) return; for (i = 0; i < level.tws_connect_handlers.size; i++) self thread [[ level.tws_connect_handlers[i] ]](); } // handlers own their own self endon("disconnect") on_player_spawned_dispatch() { self endon("disconnect"); self waittill("spawned_player"); if (!isDefined(level.tws_spawn_handlers)) return; for (i = 0; i < level.tws_spawn_handlers.size; i++) self thread [[ level.tws_spawn_handlers[i] ]](); } make_text_elem(player, x, y, align_x, scale, colour) { elem = NewClientHudElem(player); elem.horzAlign = "left"; elem.vertAlign = "top"; elem.alignX = align_x; elem.alignY = "top"; elem.x = x; elem.y = y; elem.foreground = 1; elem.fontScale = scale; elem.alpha = 1; elem.color = colour; // black glow reads as an outline against bright geometry elem.glowColor = (0, 0, 0); elem.glowAlpha = 0.6; return elem; } make_shader_elem(player, x, y) { elem = NewClientHudElem(player); elem.horzAlign = "left"; elem.vertAlign = "top"; elem.alignX = "left"; elem.alignY = "top"; elem.x = x; elem.y = y; elem.foreground = 1; elem.alpha = 1; return elem; } destroy_on_disconnect(elems) { self waittill("disconnect"); for (i = 0; i < elems.size; i++) { if (isDefined(elems[i])) elems[i] Destroy(); } } int_dvar_or(name, fallback) { raw = GetDvar(name); if (raw == "") return fallback; return int(raw); } format_mmss(elapsed_ms) { total_secs = int(elapsed_ms / 1000); if (total_secs < 0) total_secs = 0; mins = int(total_secs / 60); secs = total_secs - (mins * 60); sec_str = "" + secs; if (secs < 10) sec_str = "0" + secs; return "" + mins + ":" + sec_str; }