From c1399804193e8402fcd6dbf2e97bea826393a192 Mon Sep 17 00:00:00 2001
From: Aaron Dean <8dino2@gmail.com>
Date: Tue, 5 Sep 2023 15:02:58 -0400
Subject: [PATCH] Moved to action dir
---
actionlite/CMakeLists.txt | 5 +-
actionlite/action/a_balancer.cpp | 99 +
actionlite/action/a_game.cpp | 1108 ++++++++++
actionlite/action/a_game.h | 60 +
actionlite/action/a_radio.cpp | 732 +++++++
actionlite/action/a_radio.h | 79 +
actionlite/action/a_team.cpp | 2945 +++++++++++++++++++++++++++
actionlite/action/a_team.h | 110 +
actionlite/action/cgf_sfx_glass.cpp | 741 +++++++
actionlite/action/cgf_sfx_glass.h | 83 +
actionlite/g_local.h | 40 +-
actionlite/g_main.cpp | 12 +-
actionlite/q_std.h | 28 +
13 files changed, 6027 insertions(+), 15 deletions(-)
create mode 100644 actionlite/action/a_balancer.cpp
create mode 100644 actionlite/action/a_game.cpp
create mode 100644 actionlite/action/a_game.h
create mode 100644 actionlite/action/a_radio.cpp
create mode 100644 actionlite/action/a_radio.h
create mode 100644 actionlite/action/a_team.cpp
create mode 100644 actionlite/action/a_team.h
create mode 100644 actionlite/action/cgf_sfx_glass.cpp
create mode 100644 actionlite/action/cgf_sfx_glass.h
diff --git a/actionlite/CMakeLists.txt b/actionlite/CMakeLists.txt
index d6fcdde..84b2085 100644
--- a/actionlite/CMakeLists.txt
+++ b/actionlite/CMakeLists.txt
@@ -7,10 +7,9 @@ set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
set(GAME_SRC
- ${CMAKE_CURRENT_SOURCE_DIR}/action/a_team.cpp
${CMAKE_CURRENT_SOURCE_DIR}/action/a_game.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/action/a_match.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/action/a_radio.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/action/a_team.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/action/a_balancer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/action/cgf_sfx_glass.cpp
${CMAKE_CURRENT_SOURCE_DIR}/bots/bot_debug.cpp
diff --git a/actionlite/action/a_balancer.cpp b/actionlite/action/a_balancer.cpp
new file mode 100644
index 0000000..6045e6d
--- /dev/null
+++ b/actionlite/action/a_balancer.cpp
@@ -0,0 +1,99 @@
+#include "../g_local.h"
+
+cvar_t *eventeams;
+cvar_t *use_balancer;
+
+edict_t *FindNewestPlayer(int team)
+{
+ edict_t *e, *newest = NULL;
+ int i;
+
+ for (i = 0, e = &g_edicts[1]; i < game.maxclients; i++, e++)
+ {
+ if (!e->inuse || e->client->resp.team != team)
+ continue;
+
+ if (!newest || e->client->resp.joined_team > newest->client->resp.joined_team) {
+ newest = e;
+ }
+ }
+
+ return newest;
+}
+
+void CalculatePlayers(int *players)
+{
+ edict_t *e;
+ int i;
+
+ for (i = 0, e = &g_edicts[1]; i < game.maxclients; i++, e++)
+ {
+ if (!e->inuse)
+ continue;
+
+ players[e->client->resp.team]++;
+ }
+}
+
+/* parameter can be current (dead) player or null */
+bool CheckForUnevenTeams (edict_t *ent)
+{
+ edict_t *swap_ent = NULL;
+ int i, other_team, players[TEAM_TOP] = {0}, leastPlayers, mostPlayers;
+
+ if(!use_balancer->value)
+ return false;
+
+ CalculatePlayers(players);
+
+ leastPlayers = mostPlayers = TEAM1;
+ for (i = TEAM1; i <= teamCount; i++) {
+ if (players[i] > players[mostPlayers])
+ mostPlayers = i;
+
+ if (players[i] < players[leastPlayers])
+ leastPlayers = i;
+ }
+ if (players[mostPlayers] > players[leastPlayers] + 1) {
+ other_team = leastPlayers;
+ swap_ent = FindNewestPlayer(mostPlayers);
+ }
+
+ if(swap_ent && (!ent || ent == swap_ent)) {
+ gi.Client_Print (swap_ent, PRINT_HIGH, "You have been swapped to the other team to even the game.");
+ gi.sound(swap_ent, CHAN_ITEM, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+ swap_ent->client->team_force = true;
+ JoinTeam(swap_ent, other_team, 1);
+ return true;
+ }
+
+ return false;
+}
+
+bool IsAllowedToJoin(edict_t *ent, int desired_team)
+{
+ int i, players[TEAM_TOP] = {0}, mostPlayers;
+
+ if(ent->client->team_force) {
+ ent->client->team_force = false;
+ return true;
+ }
+
+ CalculatePlayers(players);
+
+ mostPlayers = 0;
+ for (i = TEAM1; i <= teamCount; i++) {
+ if (i == desired_team)
+ continue;
+
+ if (!mostPlayers || players[i] > players[mostPlayers])
+ mostPlayers = i;
+ }
+
+ /* can join both teams if they are even and can join if the other team has less players than current */
+ if (players[desired_team] < players[mostPlayers] ||
+ (ent->client->resp.team == NOTEAM && players[desired_team] == players[mostPlayers]))
+ return true;
+ return false;
+}
+
diff --git a/actionlite/action/a_game.cpp b/actionlite/action/a_game.cpp
new file mode 100644
index 0000000..7b844d3
--- /dev/null
+++ b/actionlite/action/a_game.cpp
@@ -0,0 +1,1108 @@
+#include "../g_local.h"
+
+#define MAX_MAP_ROTATION 1000 // just in case...
+#define MAX_STR_LEN 1000
+#define MAX_TOTAL_MOTD_LINES 30
+
+char *map_rotation[MAX_MAP_ROTATION];
+int num_maps, cur_map, rand_map, num_allvotes; // num_allvotes added by Igor[Rock]
+
+char motd_lines[MAX_TOTAL_MOTD_LINES][40];
+int motd_num_lines;
+
+/*
+ * ReadConfigFile()
+ * Config file format is backwards compatible with Action's, but doesn't need
+ * the "###" designator at end of sections.
+ * -Fireblade
+ */
+void ReadConfigFile()
+{
+ FILE *config_file;
+ char buf[MAX_STR_LEN], reading_section[MAX_STR_LEN], inipath[MAX_STR_LEN];
+ int lines_into_section = -1;
+ cvar_t *ininame;
+
+ ininame = gi.cvar("ininame", "action.ini", CVAR_NOFLAGS);
+ if (ininame->string && *(ininame->string))
+ sprintf(inipath, "%s/%s", GAMEVERSION, ininame->string);
+ else
+ sprintf(inipath, "%s/%s", GAMEVERSION, "action.ini");
+
+ config_file = fopen(inipath, "r");
+ if (config_file == NULL) {
+ gi.Com_PrintFmt("Unable to read %s\n", inipath);
+ return;
+ }
+
+ while (fgets(buf, MAX_STR_LEN - 10, config_file) != NULL) {
+ int bs;
+
+ bs = strlen(buf);
+ while (buf[bs - 1] == '\r' || buf[bs - 1] == '\n') {
+ buf[bs - 1] = 0;
+ bs--;
+ }
+
+ if ((buf[0] == '/' && buf[1] == '/') || buf[0] == 0) {
+ continue;
+ }
+
+ if (buf[0] == '[') {
+ char *p;
+
+ p = strchr(buf, ']');
+ if (p == NULL)
+ continue;
+ *p = 0;
+ strcpy(reading_section, buf + 1);
+ lines_into_section = 0;
+ continue;
+ }
+ if (buf[0] == '#' && buf[1] == '#' && buf[2] == '#') {
+ lines_into_section = -1;
+ continue;
+ }
+ if (lines_into_section > -1) {
+ if (!strcmp(reading_section, "team1")) {
+ if (lines_into_section == 0) {
+ Q_strlcpy(teams[TEAM1].name, buf, sizeof(teams[TEAM1].name));
+ } else if (lines_into_section == 1) {
+ Q_strlcpy(teams[TEAM1].skin, buf, sizeof(teams[TEAM1].skin));
+ }
+ } else if (!strcmp(reading_section, "team2")) {
+ if (lines_into_section == 0) {
+ Q_strlcpy(teams[TEAM2].name, buf, sizeof(teams[TEAM2].name));
+ } else if (lines_into_section == 1) {
+ Q_strlcpy(teams[TEAM2].skin, buf, sizeof(teams[TEAM2].skin));
+ }
+ } else if (!strcmp(reading_section, "team3")) {
+ if (lines_into_section == 0) {
+ Q_strlcpy(teams[TEAM3].name, buf, sizeof(teams[TEAM3].name));
+ } else if (lines_into_section == 1) {
+ Q_strlcpy(teams[TEAM3].skin, buf, sizeof(teams[TEAM3].skin));
+ }
+ } else if (!strcmp(reading_section, "maplist")) {
+ map_rotation[num_maps] = (char *) gi.TagMalloc(strlen(buf) + 1, TAG_GAME);
+ strcpy(map_rotation[num_maps], buf);
+ num_maps++;
+ }
+ lines_into_section++;
+ }
+ }
+
+ snprintf(teams[TEAM1].skin_index, sizeof(teams[TEAM1].skin_index), "../players/%s_i", teams[TEAM1].skin);
+ snprintf(teams[TEAM2].skin_index, sizeof(teams[TEAM2].skin_index), "../players/%s_i", teams[TEAM2].skin);
+ snprintf(teams[TEAM3].skin_index, sizeof(teams[TEAM3].skin_index), "../players/%s_i", teams[TEAM3].skin);
+
+ cur_map = 0;
+ srand(time(NULL));
+ rand_map = (num_maps > 1) ? (rand() % (num_maps - 1) + 1) : 1;
+
+ fclose(config_file);
+}
+
+void ReadMOTDFile()
+{
+ FILE *motd_file;
+ char buf[1000];
+ char motdpath[MAX_STR_LEN];
+ int lbuf;
+ cvar_t *motdname;
+
+ motdname = gi.cvar("motdname", "motd.txt", CVAR_NOFLAGS);
+ if (motdname->string && *(motdname->string))
+ sprintf(motdpath, "%s/%s", GAMEVERSION, motdname->string);
+ else
+ sprintf(motdpath, "%s/%s", GAMEVERSION, "motd.txt");
+
+ motd_file = fopen(motdpath, "r");
+ if (motd_file == NULL)
+ return;
+
+ motd_num_lines = 0;
+ while (fgets(buf, 900, motd_file) != NULL) {
+ lbuf = strlen(buf);
+ while (lbuf > 0 && (buf[lbuf - 1] == '\r' || buf[lbuf - 1] == '\n')) {
+ buf[lbuf - 1] = 0;
+ lbuf--;
+ }
+
+ if(!lbuf)
+ continue;
+
+ if (lbuf > 39)
+ buf[39] = 0;
+
+ strcpy(motd_lines[motd_num_lines++], buf);
+
+ if (motd_num_lines >= MAX_TOTAL_MOTD_LINES)
+ break;
+ }
+
+ fclose(motd_file);
+}
+
+// AQ2:TNG Deathwatch - Ohh, lovely MOTD - edited it
+void PrintMOTD(edict_t * ent)
+{
+ int mapnum, i, lines = 0;
+ int max_lines = MAX_TOTAL_MOTD_LINES;
+ char msg_buf[1024];
+ const char *server_type;
+
+
+ //Welcome Message. This shows the Version Number and website URL, followed by an empty line
+ strcpy(msg_buf, TNG_TITLE " v" VERSION "\n" "https://github.com/actionquake/quake2-rerelease-dll" "\n\n");
+ lines = 3;
+
+ /*
+ As long as we don't skip the MOTD, we want to print all information
+ */
+ //if (!skipmotd->value) {
+ // This line will show the hostname. If not set, the default name will be "Unnamed TNG Server" (used to be "unnamed")
+ if (hostname->string[0] && strcmp(hostname->string, "Unnamed TNG Server"))
+ {
+ Q_strlcat(msg_buf, hostname->string, strlen(msg_buf)+40);
+ strcat(msg_buf, "\n");
+ lines++;
+ }
+
+ /*
+ Now all the settings
+ */
+
+ // Check what game type it is
+ if (teamplay->value)
+ {
+ if (teamdm->value) // Is it TeamDM?
+ {
+ if (teamCount == 3)
+ server_type = "3 Team Deathmatch";
+ else
+ server_type = "Team Deathmatch";
+ }
+ }
+ else // So it's not Teamplay?
+ {
+ server_type = "Deathmatch (Free For All)";
+ sprintf(msg_buf + strlen(msg_buf), "Game Type: %s\n", server_type);
+ }
+ lines++;
+
+ // Adding an empty line
+ strcat(msg_buf, "\n");
+ lines++;
+
+ /*
+ Now for the map rules, such as Timelimit, Roundlimit, etc
+ */
+ if (fraglimit->integer) // What is the fraglimit?
+ sprintf(msg_buf + strlen(msg_buf), "Fraglimit: %d", fraglimit->integer);
+ else
+ strcat(msg_buf, "Fraglimit: none");
+
+ if (timelimit->integer) // What is the timelimit?
+ sprintf(msg_buf + strlen(msg_buf), " Timelimit: %d\n", timelimit->integer);
+ else
+ strcat(msg_buf, " Timelimit: none\n");
+ lines++;
+
+ // If we're in Teamplay, and not CTF, we want to see what the roundlimit and roundtimelimit is
+ if (gameSettings & GS_ROUNDBASED)
+ {
+ if ((int)roundlimit->value) // What is the roundlimit?
+ sprintf(msg_buf + strlen(msg_buf), "Roundlimit: %d", (int)roundlimit->value);
+ else
+ strcat(msg_buf, "Roundlimit: none");
+ lines++;
+ }
+
+ /*
+ Check for the number of weapons and items people can carry
+ */
+ if ((int)unique_weapons->value != 1 || (int)unique_items->value != 1) {
+ sprintf(msg_buf + strlen(msg_buf), "Max number of spec weapons: %d items: %d\n",
+ (int) unique_weapons->value, (int) unique_items->value);
+ lines++;
+ }
+
+ /*
+ What can we use with the Bandolier?
+ */
+ if (tgren->value > 0 || !(ir->value)) {
+ char grenade_num[32];
+
+ // Show the number of grenades with the Bandolier
+ if (tgren->value > 0)
+ sprintf(grenade_num, "%d grenade%s", (int)tgren->value, (int)tgren->value == 1 ? "" : "s");
+
+ sprintf(msg_buf + strlen(msg_buf), "Bandolier w/ %s%s%s\n",
+ !(ir->value) ? "no IR" : "",
+ (tgren->value > 0 && !(ir->value)) ? " & " : "",
+ tgren->value > 0 ? grenade_num : "");
+ lines++;
+ }
+
+ /*
+ Is allitem and/or allweapon enabled?
+ */
+ if (allitem->value || allweapon->value) {
+ sprintf(msg_buf + strlen(msg_buf), "Players receive %s%s%s\n",
+ allweapon->value ? "all weapons" : "",
+ (allweapon->value && allitem->value) ? " & " : "",
+ allitem->value ? "all items" : "");
+ lines++;
+ }
+
+ /*
+ * Are we using limchasecam?
+ */
+ if (limchasecam->value) {
+ if ((int) limchasecam->value == 2)
+ sprintf(msg_buf + strlen(msg_buf), "Chase Cam Disallowed\n");
+ else
+ sprintf(msg_buf + strlen(msg_buf), "Chase Cam Restricted\n");
+ lines++;
+ }
+
+ /*
+ * Are the dmflags set to disallow Friendly Fire?
+ */
+ if (teamplay->value && !g_friendly_fire->integer) {
+ sprintf(msg_buf + strlen(msg_buf), "Friendly Fire Enabled\n");
+ lines++;
+ }
+
+ /*
+ Are we using any types of voting?
+ */
+ // if (use_mapvote->value || use_cvote->value || use_kickvote->value) {
+ // sprintf(msg_buf + strlen(msg_buf), "Vote Types: %s%s%s%s%s\n",
+ // use_mapvote->value ? "Map" : "", (use_mapvote->value
+ // && use_cvote->value) ? " & " : "",
+ // use_cvote->value ? "Config" : "", ((use_mapvote->value && use_kickvote->value)
+ // || (use_cvote->value
+ // && use_kickvote->value)) ? " & " : "",
+ // use_kickvote->value ? "Kick" : "");
+ // lines++; // lines+=3;
+ // }
+
+ /*
+ Map Locations
+ */
+ if (ml_count != 0) {
+ sprintf(msg_buf + strlen(msg_buf), "\n%d Locations, by: %s\n", ml_count, ml_creator);
+ lines++;
+ }
+ /*
+ If actionmaps, put a blank line then the maps list
+ */
+ if (actionmaps->value && num_maps > 0)
+ {
+ int chars_on_line = 0, len_mr;
+
+ if (vrot->value) // Using Vote Rotation?
+ strcat(msg_buf, "\nRunning these maps in vote order:\n");
+ else if (rrot->value) // Using Random Rotation?
+ strcat(msg_buf, "\nRunning the following maps randomly:\n");
+ else
+ strcat(msg_buf, "\nRunning the following maps in order:\n");
+
+ lines += 2;
+
+ for (mapnum = 0; mapnum < num_maps; mapnum++)
+ {
+ len_mr = strlen(*(map_rotation + mapnum));
+ if ((chars_on_line + len_mr + 2) > 39) {
+ Q_strlcat(msg_buf, "\n", sizeof(msg_buf));
+ lines++;
+ if (lines >= max_lines)
+ break;
+ chars_on_line = 0;
+ }
+ Q_strlcat(msg_buf, *(map_rotation + mapnum), sizeof(msg_buf));
+ chars_on_line += len_mr;
+ if (mapnum < (num_maps - 1)) {
+ Q_strlcat(msg_buf, ", ", sizeof(msg_buf));
+ chars_on_line += 2;
+ }
+ }
+
+ if (lines < max_lines) {
+ Q_strlcat(msg_buf, "\n", sizeof(msg_buf));
+ lines++;
+ }
+ }
+
+ //If we're in teamplay, we want to inform people that they can open the menu with TAB
+ if (teamplay->value && lines < max_lines && !auto_menu->value) {
+ Q_strlcat(msg_buf, "\nHit TAB to open the Team selection menu", sizeof(msg_buf));
+ lines++;
+ }
+ //}
+
+ /*
+ Insert action/motd.txt contents (whole MOTD gets truncated after 30 lines)
+ */
+
+ if (motd_num_lines && lines < max_lines-1)
+ {
+ Q_strlcat(msg_buf, "\n", sizeof(msg_buf));
+ lines++;
+ for (i = 0; i < motd_num_lines; i++) {
+ Q_strlcat(msg_buf, motd_lines[i], sizeof(msg_buf));
+ lines++;
+ if (lines >= max_lines)
+ break;
+ Q_strlcat(msg_buf, "\n", sizeof(msg_buf));
+ }
+ }
+
+ if (!auto_menu->value || ent->client->pers.menu_shown) {
+ gi.LocCenter_Print(ent, "%s", msg_buf);
+ } else {
+ gi.LocClient_Print(ent, PRINT_LOW, "%s", msg_buf);
+ }
+}
+
+// stuffcmd: forces a player to execute a command.
+void stuffcmd(edict_t * ent, char *c)
+{
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString(c);
+ gi.unicast(ent, true);
+}
+
+
+// void unicastSound(edict_t *ent, int soundIndex, float volume)
+// {
+// int mask = MASK_ENTITY_CHANNEL;
+
+// if (volume != 1.0)
+// mask |= MASK_VOLUME;
+
+// gi.WriteByte(svc_sound);
+// gi.WriteByte((byte)mask);
+// gi.WriteByte((byte)soundIndex);
+// if (mask & MASK_VOLUME)
+// gi.WriteByte((byte)(volume * 255));
+
+// // hack when first person spectating, the sound source must be the spectated player
+// if (ent->client->chase_mode == 2 && ent->client->chase_target) {
+// gi.WriteShort(((ent->client->chase_target - g_edicts - 1) << 3) + CHAN_NO_PHS_ADD);
+// } else {
+// gi.WriteShort(((ent - g_edicts - 1) << 3) + CHAN_NO_PHS_ADD);
+// }
+
+// gi.unicast (ent, true);
+// }
+// AQ2:TNG END
+
+/********************************************************************************
+*
+* zucc: following are EjectBlooder, EjectShell, AddSplat, and AddDecal
+* code. All from actionquake, some had to be modified to fit Axshun or fix
+* bugs.
+*
+*/
+
+// int decals = 0;
+// int shells = 0;
+// int splats = 0;
+
+// //blooder used for bleeding
+
+// void BlooderTouch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
+// {
+// if( (other == self->owner) || other->client ) // Don't stop on players.
+// return;
+// self->think = G_FreeEdict;
+// self->nextthink = level.time + 1_ms;
+// }
+
+// void EjectBlooder(edict_t * self, vec3_t start, vec3_t veloc)
+// {
+// edict_t *blooder;
+// vec3_t forward;
+// int spd = 0;
+
+// blooder = G_Spawn();
+// VectorCopy(veloc, forward);
+// VectorCopy(start, blooder->s.origin);
+// //VectorCopy(start, blooder->old_origin);
+// spd = 0;
+// VectorScale(forward, spd, blooder->velocity);
+// blooder->solid = SOLID_NOT;
+// blooder->movetype = MOVETYPE_BLOOD; // Allow dripping blood to make a splat.
+// blooder->s.modelindex = level.model_null;
+// blooder->s.effects |= EF_GIB;
+// blooder->owner = self;
+// blooder->touch = BlooderTouch;
+// blooder->nextthink = level.time + 32_ms;
+// blooder->think = G_FreeEdict;
+// blooder->classname = "blooder";
+
+// gi.linkentity(blooder);
+// }
+
+// zucc - Adding EjectShell code from action quake, modified for Axshun.
+/********* SHELL EJECTION **************/
+
+void PlaceHolder( edict_t * ent ); // p_weapon.c
+
+// void ShellTouch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
+// {
+// if (self->owner->client->pers.weapon->id == IT_WEAPON_M3)
+// gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/shellhit1.wav"), 1, ATTN_STATIC, 0);
+// else if (random() < 0.5)
+// gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/tink1.wav"), 0.2, ATTN_STATIC, 0);
+// else
+// gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/tink2.wav"), 0.2, ATTN_STATIC, 0);
+// }
+
+// void ShellDie(edict_t * self)
+// {
+// G_FreeEdict(self);
+// --shells;
+// }
+
+// static void RemoveOldestShell( void )
+// {
+// int i = 0;
+// edict_t *it = NULL, *found = NULL;
+
+// for( i = 0; i < globals.num_edicts; i ++ )
+// {
+// it = &g_edicts[i];
+// if( it->inuse && it->classname && (strcmp( it->classname, "shell" ) == 0) )
+// {
+// if( (! found) || (it->freetime < found->freetime) )
+// found = it;
+// }
+// }
+
+// if( found )
+// ShellDie( found );
+// }
+
+// // zucc fixed this so it works with the sniper rifle and checks handedness
+// // had to add the toggle feature to handle the akimbos correctly, if 1
+// // it sets up for ejecting the shell from the left akimbo weapon, if 2
+// // it fires right handed akimbo
+
+// void EjectShell(edict_t * self, vec3_t start, int toggle)
+// {
+// edict_t *shell;
+// vec3_t forward, right, up;
+// float r;
+// float fix = 1.0;
+// int left = 0;
+
+// // if (sv_shelloff->value)
+// // return;
+
+// // if( (shelllimit->value > 0) && (shells >= shelllimit->value) )
+// // RemoveOldestShell();
+
+// shell = G_Spawn();
+// ++shells;
+
+// AngleVectors(self->client->v_angle, forward, right, up);
+
+// if (self->client->pers.hand == LEFT_HANDED) {
+// left = 1;
+// fix = -1.0;
+// } else if (self->client->pers.hand == CENTER_HANDED)
+// fix = 0;
+
+// // zucc spent a fair amount of time hacking these until they look ok,
+// // several of them could be improved however.
+
+// if (self->client->pers.weapon->id == IT_WEAPON_MK23) {
+// VectorMA(start, left ? -7 : .4, right, start);
+// VectorMA(start, left ? 5 : 2, forward, start);
+// VectorMA(start, left ? -10 : -8, up, start);
+// } else if (self->client->pers.weapon->id == IT_WEAPON_M4) {
+// VectorMA(start, left ? -10 : 5, right, start);
+// VectorMA(start, left ? 6 : 12, forward, start);
+// VectorMA(start, left ? -9 : -11, up, start);
+// } else if (self->client->pers.weapon->id == IT_WEAPON_MP5) {
+// VectorMA(start, left ? -10 : 6, right, start);
+// VectorMA(start, left ? 6 : 8, forward, start);
+// VectorMA(start, left ? -9 : -10, up, start);
+// } else if (self->client->pers.weapon->id == IT_WEAPON_SNIPER) {
+// VectorMA(start, fix * 11, right, start);
+// VectorMA(start, 2, forward, start);
+// VectorMA(start, -11, up, start);
+
+// } else if (self->client->pers.weapon->id == IT_WEAPON_M3) {
+// VectorMA(start, left ? -9 : 3, right, start);
+// VectorMA(start, left ? 4 : 4, forward, start);
+// VectorMA(start, left ? -1 : -1, up, start);
+// }
+
+// else if (self->client->pers.weapon->id == IT_WEAPON_DUALMK23) {
+// if (self->client->pers.hand == LEFT_HANDED)
+// VectorMA(start, ((toggle == 1) ? 8 : -8), right, start);
+// else
+// VectorMA(start, ((toggle == 1) ? -4 : 4), right, start);
+// VectorMA(start, 6, forward, start);
+// VectorMA(start, -9, up, start);
+
+// }
+
+// if ((forward[2] >= -1) && (forward[2] < -0.99)) {
+// VectorMA(start, 5, forward, start);
+// VectorMA(start, -0.5, up, start);
+// } else if ((forward[2] >= -0.99) && (forward[2] < -0.98)) {
+// VectorMA(start, 5, forward, start);
+// VectorMA(start, -.1, up, start);
+// } else if ((forward[2] >= -0.98) && (forward[2] < -0.97)) {
+// VectorMA(start, 5.1, forward, start);
+// VectorMA(start, 0.3, up, start);
+// } else if ((forward[2] >= -0.97) && (forward[2] < -0.96)) {
+// VectorMA(start, 5.2, forward, start);
+// VectorMA(start, 0.7, up, start);
+// } else if ((forward[2] >= -0.96) && (forward[2] < -0.95)) {
+// VectorMA(start, 5.2, forward, start);
+// VectorMA(start, 1.1, up, start);
+// } else if ((forward[2] >= -0.95) && (forward[2] < -0.94)) {
+// VectorMA(start, 5.3, forward, start);
+// VectorMA(start, 1.5, up, start);
+// } else if ((forward[2] >= -0.94) && (forward[2] < -0.93)) {
+// VectorMA(start, 5.4, forward, start);
+// VectorMA(start, 1.9, up, start);
+// } else if ((forward[2] >= -0.93) && (forward[2] < -0.92)) {
+// VectorMA(start, 5.5, forward, start);
+// VectorMA(start, 2.3, up, start);
+// } else if ((forward[2] >= -0.92) && (forward[2] < -0.91)) {
+// VectorMA(start, 5.6, forward, start);
+// VectorMA(start, 2.7, up, start);
+// } else if ((forward[2] >= -0.91) && (forward[2] < -0.9)) {
+// VectorMA(start, 5.7, forward, start);
+// VectorMA(start, 3.1, up, start);
+// } else if ((forward[2] >= -0.9) && (forward[2] < -0.85)) {
+// VectorMA(start, 5.8, forward, start);
+// VectorMA(start, 3.5, up, start);
+// } else if ((forward[2] >= -0.85) && (forward[2] < -0.8)) {
+// VectorMA(start, 6, forward, start);
+// VectorMA(start, 4, up, start);
+// } else if ((forward[2] >= -0.8) && (forward[2] < -0.6)) {
+// VectorMA(start, 6.5, forward, start);
+// VectorMA(start, 4.5, up, start);
+// } else if ((forward[2] >= -0.6) && (forward[2] < -0.4)) {
+// VectorMA(start, 8, forward, start);
+// VectorMA(start, 5.5, up, start);
+// } else if ((forward[2] >= -0.4) && (forward[2] < -0.2)) {
+// VectorMA(start, 9.5, forward, start);
+// VectorMA(start, 6, up, start);
+// } else if ((forward[2] >= -0.2) && (forward[2] < 0)) {
+// VectorMA(start, 11, forward, start);
+// VectorMA(start, 6.5, up, start);
+// } else if ((forward[2] >= 0) && (forward[2] < 0.2)) {
+// VectorMA(start, 12, forward, start);
+// VectorMA(start, 7, up, start);
+// } else if ((forward[2] >= 0.2) && (forward[2] < 0.4)) {
+// VectorMA(start, 14, forward, start);
+// VectorMA(start, 6.5, up, start);
+// } else if ((forward[2] >= 0.4) && (forward[2] < 0.6)) {
+// VectorMA(start, 16, forward, start);
+// VectorMA(start, 6, up, start);
+// } else if ((forward[2] >= 0.6) && (forward[2] < 0.8)) {
+// VectorMA(start, 18, forward, start);
+// VectorMA(start, 5, up, start);
+// } else if ((forward[2] >= 0.8) && (forward[2] < 0.85)) {
+// VectorMA(start, 18, forward, start);
+// VectorMA(start, 4, up, start);
+// } else if ((forward[2] >= 0.85) && (forward[2] < 0.9)) {
+// VectorMA(start, 18, forward, start);
+// VectorMA(start, 2.5, up, start);
+// } else if ((forward[2] >= 0.9) && (forward[2] < 0.91)) {
+// VectorMA(start, 18.2, forward, start);
+// VectorMA(start, 2.2, up, start);
+// } else if ((forward[2] >= 0.91) && (forward[2] < 0.92)) {
+// VectorMA(start, 18.4, forward, start);
+// VectorMA(start, 1.9, up, start);
+// } else if ((forward[2] >= 0.92) && (forward[2] < 0.93)) {
+// VectorMA(start, 18.6, forward, start);
+// VectorMA(start, 1.6, up, start);
+// } else if ((forward[2] >= 0.93) && (forward[2] < 0.94)) {
+// VectorMA(start, 18.8, forward, start);
+// VectorMA(start, 1.3, up, start);
+// } else if ((forward[2] >= 0.94) && (forward[2] < 0.95)) {
+// VectorMA(start, 19, forward, start);
+// VectorMA(start, 1, up, start);
+// } else if ((forward[2] >= 0.95) && (forward[2] < 0.96)) {
+// VectorMA(start, 19.2, forward, start);
+// VectorMA(start, 0.7, up, start);
+// } else if ((forward[2] >= 0.96) && (forward[2] < 0.97)) {
+// VectorMA(start, 19.4, forward, start);
+// VectorMA(start, 0.4, up, start);
+// } else if ((forward[2] >= 0.97) && (forward[2] < 0.98)) {
+// VectorMA(start, 19.6, forward, start);
+// VectorMA(start, -0.2, up, start);
+// } else if ((forward[2] >= 0.98) && (forward[2] < 0.99)) {
+// VectorMA(start, 19.8, forward, start);
+// VectorMA(start, -0.6, up, start);
+// } else if ((forward[2] >= 0.99) && (forward[2] <= 1)) {
+// VectorMA(start, 20, forward, start);
+// VectorMA(start, -1, up, start);
+// }
+
+// VectorCopy(start, shell->s.origin);
+// //VectorCopy(start, shell->old_origin);
+// if (fix == 0) // we want some velocity on those center handed ones
+// fix = 1;
+// if (self->client->pers.weapon->id == IT_WEAPON_SNIPER)
+// VectorMA(shell->velocity, fix * (-35 + random() * -60), right, shell->velocity);
+// else if (self->client->pers.weapon->id == IT_WEAPON_DUALMK23) {
+// if (self->client->pers.hand == LEFT_HANDED)
+// VectorMA(shell->velocity,
+// (toggle == 1 ? 1 : -1) * (35 + random() * 60), right, shell->velocity);
+// else
+// VectorMA(shell->velocity,
+// (toggle == 1 ? -1 : 1) * (35 + random() * 60), right, shell->velocity);
+// } else
+// VectorMA(shell->velocity, fix * (35 + random() * 60), right, shell->velocity);
+// VectorMA(shell->avelocity, 500, right, shell->avelocity);
+// if (self->client->pers.weapon->id == IT_WEAPON_SNIPER)
+// VectorMA(shell->velocity, 60 + 40, up, shell->velocity);
+// else
+// VectorMA(shell->velocity, 60 + random() * 90, up, shell->velocity);
+
+// shell->movetype = MOVETYPE_BOUNCE;
+// shell->solid = SOLID_BBOX;
+
+// if( (self->client->pers.weapon->id == IT_WEAPON_M3) || (self->client->pers.weapon->id == IT_WEAPON_HANDCANNON) )
+// shell->s.modelindex = gi.modelindex("models/weapons/shell/tris2.md2");
+// else if( (self->client->pers.weapon->id == IT_WEAPON_SNIPER) || (self->client->pers.weapon->id == IT_WEAPON_M4) )
+// shell->s.modelindex = gi.modelindex("models/weapons/shell/tris3.md2");
+// else
+// shell->s.modelindex = gi.modelindex("models/weapons/shell/tris.md2");
+
+// r = random();
+// if (r < 0.1)
+// shell->s.frame = 0;
+// else if (r < 0.2)
+// shell->s.frame = 1;
+// else if (r < 0.3)
+// shell->s.frame = 2;
+// else if (r < 0.5)
+// shell->s.frame = 3;
+// else if (r < 0.6)
+// shell->s.frame = 4;
+// else if (r < 0.7)
+// shell->s.frame = 5;
+// else if (r < 0.8)
+// shell->s.frame = 6;
+// else if (r < 0.9)
+// shell->s.frame = 7;
+// else
+// shell->s.frame = 8;
+
+// shell->owner = self;
+// shell->touch = ShellTouch;
+// shell->nextthink = level.framenum + (shelllife->value - (shells * 0.05)) * HZ;
+// shell->think = shelllife->value ? ShellDie : PlaceHolder;
+// shell->classname = "shell";
+// shell->freetime = level.time; // Used to determine oldest spawned shell.
+
+// gi.linkentity(shell);
+// }
+
+/*
+ ==================
+ FindEdictByClassnum
+ ==================
+ */
+edict_t *FindEdictByClassnum(char *classname, int classnum)
+{
+ int i;
+ edict_t *it;
+
+ for (i = 0; i < globals.num_edicts; i++)
+ {
+ it = &g_edicts[i];
+ if (it->classname && (it->classnum == classnum) && (Q_stricmp(it->classname, classname) == 0))
+ return it;
+ }
+
+ return NULL;
+
+}
+
+// /********* Bulletholes/wall stuff ***********/
+
+// void UpdateAttachedPos( edict_t *self )
+// {
+// vec3_t fwd, right, up;
+
+// if( (self->wait && (level.framenum >= self->wait)) || ! self->movetarget->inuse )
+// {
+// G_FreeEdict(self);
+// return;
+// }
+
+// self->nextthink = level.framenum + 1;
+
+// if( self < self->movetarget )
+// {
+// // If the object we're attached to hasn't been updated yet this frame,
+// // we need to move ahead one frame's worth so we stay aligned with it.
+// VectorScale( self->movetarget->velocity, FRAMETIME, self->s.origin );
+// VectorAdd( self->movetarget->s.origin, self->s.origin, self->s.origin );
+// VectorScale( self->movetarget->avelocity, FRAMETIME, self->s.angles );
+// VectorAdd( self->movetarget->s.angles, self->s.angles, self->s.angles );
+// }
+// else
+// {
+// VectorCopy( self->movetarget->s.origin, self->s.origin );
+// VectorCopy( self->movetarget->s.angles, self->s.angles );
+// }
+
+// AngleVectors( self->s.angles, fwd, right, up ); // At this point, this is the angles of the entity we attached to.
+// self->s.origin[0] += fwd[0] * self->move_origin[0] + right[0] * self->move_origin[1] + up[0] * self->move_origin[2];
+// self->s.origin[1] += fwd[1] * self->move_origin[0] + right[1] * self->move_origin[1] + up[1] * self->move_origin[2];
+// self->s.origin[2] += fwd[2] * self->move_origin[0] + right[2] * self->move_origin[1] + up[2] * self->move_origin[2];
+// VectorAdd( self->s.angles, self->move_angles, self->s.angles );
+// VectorCopy( self->movetarget->velocity, self->velocity );
+// VectorCopy( self->movetarget->avelocity, self->avelocity );
+// }
+
+// // Decal/splat/knife attached to some moving entity.
+// void AttachedThink( edict_t *self )
+// {
+// UpdateAttachedPos( self );
+// gi.linkentity( self );
+// }
+
+// // Attach a splat/decal/knife to a moving entity.
+// void AttachToEntity( edict_t *self, edict_t *onto )
+// {
+// vec3_t fwd, right, up, offset;
+
+// self->wait = self->nextthink; // Use old nextthink as despawn framenum (0 is never).
+// self->movetype = MOVETYPE_NONE;
+
+// self->movetarget = onto;
+// AngleVectors( onto->s.angles, fwd, right, up );
+// VectorSubtract( self->s.origin, onto->s.origin, offset );
+// self->move_origin[0] = DotProduct( offset, fwd );
+// self->move_origin[1] = DotProduct( offset, right );
+// self->move_origin[2] = DotProduct( offset, up );
+// VectorSubtract( self->s.angles, onto->s.angles, self->move_angles );
+
+// self->think = AttachedThink;
+
+// UpdateAttachedPos( self );
+// }
+
+// bool CanBeAttachedTo( const edict_t *ent )
+// {
+// return (ent && ( (Q_strnicmp( ent->classname, "func_door", 9 ) == 0)
+// || (Q_stricmp( ent->classname, "func_plat" ) == 0)
+// || (Q_stricmp( ent->classname, "func_rotating" ) == 0)
+// || (Q_stricmp( ent->classname, "func_train" ) == 0)
+// || (Q_stricmp( ent->classname, "func_button" ) == 0) ));
+// }
+
+// void AddDecal(edict_t * self, trace_t * tr)
+// {
+// edict_t *decal, *dec;
+// bool attached;
+
+// if (bholelimit->value < 1)
+// return;
+
+// attached = CanBeAttachedTo(tr->ent);
+
+// decal = bholelife->value ? G_Spawn() : G_Spawn_Decal();
+// if( ! decal )
+// return;
+
+// ++decals;
+
+// if (decals > bholelimit->value)
+// decals = 1;
+
+// dec = FindEdictByClassnum("decal", decals);
+
+// if( dec )
+// {
+// dec->think = G_FreeEdict;
+// dec->nextthink = level.framenum + FRAMEDIV;
+// }
+
+// decal->solid = SOLID_NOT;
+// decal->movetype = MOVETYPE_NONE;
+// decal->s.modelindex = gi.modelindex("models/objects/holes/hole1/hole.md2");
+// VectorCopy(tr->endpos, decal->s.origin);
+// VectorCopy(tr->endpos, decal->old_origin);
+// vectoangles(tr->plane.normal, decal->s.angles);
+// decal->s.angles[ROLL] = crandom() * 180.f;
+
+// decal->owner = self;
+// decal->touch = NULL;
+// decal->nextthink = bholelife->value ? (level.framenum + bholelife->value * HZ) : 0;
+// decal->think = bholelife->value ? G_FreeEdict : PlaceHolder;
+// decal->classname = "decal";
+// decal->classnum = decals;
+
+// if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname))) {
+// CGF_SFX_AttachDecalToGlass(tr->ent, decal);
+// }
+// else if( attached )
+// AttachToEntity( decal, tr->ent );
+
+// gi.linkentity(decal);
+// }
+
+// void AddSplat(edict_t * self, vec3_t point, trace_t * tr)
+// {
+// edict_t *splat, *spt;
+// float r;
+// bool attached;
+
+// if (splatlimit->value < 1)
+// return;
+
+// attached = CanBeAttachedTo(tr->ent);
+
+// splat = splatlife->value ? G_Spawn() : G_Spawn_Decal();
+// if( ! splat )
+// return;
+
+// ++splats;
+
+// if (splats > splatlimit->value)
+// splats = 1;
+
+// spt = FindEdictByClassnum("splat", splats);
+
+// if( spt )
+// {
+// spt->think = G_FreeEdict;
+// spt->nextthink = level.framenum + FRAMEDIV;
+// }
+
+// splat->solid = SOLID_NOT;
+// splat->movetype = MOVETYPE_NONE;
+
+// r = random();
+// if (r > .67)
+// splat->s.modelindex = gi.modelindex("models/objects/splats/splat1/splat.md2");
+// else if (r > .33)
+// splat->s.modelindex = gi.modelindex("models/objects/splats/splat2/splat.md2");
+// else
+// splat->s.modelindex = gi.modelindex("models/objects/splats/splat3/splat.md2");
+
+// VectorCopy(point, splat->s.origin);
+// VectorCopy(point, splat->old_origin);
+
+// vectoangles(tr->plane.normal, splat->s.angles);
+// splat->s.angles[ROLL] = crandom() * 180.f;
+
+// splat->owner = self;
+// splat->touch = NULL;
+// splat->nextthink = level.framenum + splatlife->value * HZ;
+// splat->think = splatlife->value ? G_FreeEdict : PlaceHolder;
+// splat->classname = "splat";
+// splat->classnum = splats;
+
+// if ((tr->ent) && (0 == Q_stricmp("func_explosive", tr->ent->classname))) {
+// CGF_SFX_AttachDecalToGlass(tr->ent, splat);
+// }
+// else if( attached )
+// AttachToEntity( splat, tr->ent );
+
+// gi.linkentity(splat);
+// }
+
+/* %-variables for chat msgs */
+
+void GetWeaponName( edict_t *ent, char *buf )
+{
+ if( IS_ALIVE(ent) && ent->client->pers.weapon )
+ {
+ strcpy( buf, ent->client->pers.weapon->pickup_name );
+ return;
+ }
+
+ strcpy( buf, "no weapon" );
+}
+
+void GetItemName( edict_t *ent, char *buf )
+{
+ int i, itemNum;
+
+ if( IS_ALIVE(ent) )
+ {
+ for( i = 0; i < ITEM_COUNT; i ++ )
+ {
+ itemNum = ITEM_FIRST + i;
+ if( INV_AMMO( ent, itemNum ) )
+ {
+ strcpy( buf, GET_ITEM(itemNum)->pickup_name );
+ return;
+ }
+ }
+ }
+
+ strcpy( buf, "no item" );
+}
+
+void GetHealth( edict_t *ent, char *buf )
+{
+ if( IS_ALIVE(ent) )
+ sprintf( buf, "%d", ent->health );
+ else
+ sprintf( buf, "0" );
+}
+
+void GetAmmo( edict_t *ent, char *buf )
+{
+ int ammo;
+
+ if( IS_ALIVE(ent) && ent->client->pers.weapon )
+ {
+ switch( ent->client->pers.weapon->id )
+ {
+ case IT_WEAPON_MK23:
+ sprintf( buf, "%d round%s (%d extra mag%s)",
+ ent->client->mk23_rds, ent->client->mk23_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_MP5:
+ sprintf( buf, "%d round%s (%d extra mag%s)",
+ ent->client->mp5_rds, ent->client->mp5_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_M4:
+ sprintf( buf, "%d round%s (%d extra mag%s)",
+ ent->client->m4_rds, ent->client->m4_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_M3:
+ sprintf( buf, "%d shell%s (%d extra shell%s)",
+ ent->client->shot_rds, ent->client->shot_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_HANDCANNON:
+ sprintf( buf, "%d shell%s (%d extra shell%s)",
+ ent->client->cannon_rds, ent->client->cannon_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_SNIPER:
+ sprintf( buf, "%d round%s (%d extra round%s)",
+ ent->client->sniper_rds, ent->client->sniper_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_DUALMK23:
+ sprintf( buf, "%d round%s (%d extra mag%s)",
+ ent->client->dual_rds, ent->client->dual_rds == 1 ? "" : "s",
+ ent->client->inventory[ent->client->ammo_index],
+ ent->client->inventory[ent->client->ammo_index] == 1 ? "" : "s");
+ return;
+ case IT_WEAPON_KNIFE:
+ ammo = INV_AMMO( ent, IT_WEAPON_KNIFE );
+ sprintf( buf, "%d kni%s", ammo, (ammo == 1) ? "fe" : "ves" );
+ return;
+ case IT_WEAPON_GRENADES:
+ ammo = INV_AMMO( ent, IT_WEAPON_GRENADES );
+ sprintf( buf, "%d grenade%s", ammo, (ammo == 1) ? "" : "s" );
+ return;
+ }
+ }
+
+ strcpy( buf, "no ammo" );
+}
+
+void GetNearbyTeammates( edict_t *self, char *buf )
+{
+ edict_t *nearby_teammates[8];
+ size_t nearby_teammates_num = 0, l;
+ edict_t *ent = NULL;
+
+ while ((ent = findradius(ent, self->s.origin, 1500)) != NULL) {
+ if (ent == self || !ent->client || !CanDamage(ent, self) || !OnSameTeam(ent, self))
+ continue;
+
+ nearby_teammates[nearby_teammates_num++] = ent;
+ if (nearby_teammates_num >= 8)
+ break;
+ }
+
+ if (nearby_teammates_num == 0) {
+ strcpy(buf, "nobody");
+ return;
+ }
+
+ strcpy(buf, nearby_teammates[0]->client->pers.netname);
+ for (l = 1; l < nearby_teammates_num; l++)
+ {
+ if (l == nearby_teammates_num - 1)
+ Q_strlcat(buf, " and ", PARSE_BUFSIZE);
+ else
+ Q_strlcat(buf, ", ", PARSE_BUFSIZE);
+
+ Q_strlcat( buf, nearby_teammates[l]->client->pers.netname, PARSE_BUFSIZE );
+ }
+}
+
+// Messaging adjectives/pronouns
+// Borrowed from https://github.com/VortexQuake2/Vortex, thank you!
+
+const char *GetPossesiveAdjectiveSingular(edict_t *ent) {
+ int gender = ent->client->pers.gender;
+ char *info;
+
+ switch( gender ) {
+ case GENDER_MALE:
+ return "his";
+ case GENDER_FEMALE:
+ return "her";
+ case GENDER_NEUTRAL:
+ return "it";
+ default:
+ return "their";
+ }
+}
+
+const char *GetPossesiveAdjective(edict_t *ent) {
+ int gender = ent->client->pers.gender;
+ char *info;
+
+ switch( gender ) {
+ case GENDER_MALE:
+ return "his";
+ case GENDER_FEMALE:
+ return "her";
+ case GENDER_NEUTRAL:
+ return "its";
+ default:
+ return "their";
+ }
+}
+
+const char *GetReflexivePronoun(edict_t *ent) {
+ int gender = ent->client->pers.gender;
+ char *info;
+
+ switch( gender ) {
+ case GENDER_MALE:
+ return "himself";
+ case GENDER_FEMALE:
+ return "herself";
+ case GENDER_NEUTRAL:
+ return "itself";
+ default:
+ return "themselves";
+ }
+}
\ No newline at end of file
diff --git a/actionlite/action/a_game.h b/actionlite/action/a_game.h
new file mode 100644
index 0000000..5b7d814
--- /dev/null
+++ b/actionlite/action/a_game.h
@@ -0,0 +1,60 @@
+// AQ2:TNG Deathwatch - Updated the Version variables to show TNG Stuff
+#ifndef VERSION
+#define VERSION "0.1"
+#endif
+#define TNG_TITLE "AQ2: AQR"
+// AQ2:TNG Deathwatch End
+//AQ2:TNG Slicer This is the max players writen on last killed target
+//SLIC2
+#define MAX_LAST_KILLED 8
+//AQ2:TNG END
+
+extern char *map_rotation[];
+extern int num_maps, cur_map, rand_map, num_allvotes; // num_allvotes added by Igor[Rock]
+
+void ReadConfigFile ();
+void ReadMOTDFile ();
+void PrintMOTD (edict_t *ent);
+void stuffcmd (edict_t *ent, char *s);
+//void unicastSound(edict_t *ent, int soundIndex, float volume);
+
+int KickDoor (trace_t * tr_old, edict_t * ent, vec3_t forward);
+
+// Prototypes of base Q2 functions that weren't included in any Q2 header
+//bool loc_CanSee (edict_t *, edict_t *);
+void ParseSayText (edict_t *, char *, size_t size);
+
+void AttachToEntity( edict_t *self, edict_t *onto );
+bool CanBeAttachedTo( const edict_t *ent );
+
+//PG BUND - BEGIN
+//void ParseSayText(edict_t *, char *);
+void GetWeaponName (edict_t * ent, char *buf);
+void GetItemName (edict_t * ent, char *buf);
+void GetHealth (edict_t * ent, char *buf);
+void GetAmmo (edict_t * ent, char *buf);
+void GetNearbyTeammates (edict_t * self, char *buf);
+
+void ResetScores (bool playerScores);
+void AddKilledPlayer (edict_t * self, edict_t * ent);
+void VideoCheckClient (edict_t * ent);
+//AQ2:TNG END
+//TempFile
+void GetLastLoss (edict_t * self, char *buf, char team);
+
+// Firing styles (where shots originate from)
+#define ACTION_FIRING_CENTER 0
+#define ACTION_FIRING_CLASSIC 1
+#define ACTION_FIRING_CLASSIC_HIGH 2
+
+// maxs[2] of a player when crouching (we modify it from the normal 4)
+// ...also the modified viewheight -FB 7/18/99
+#define CROUCHING_MAXS2 16
+#define CROUCHING_VIEWHEIGHT 8
+#define STANDING_VIEWHEIGHT 22
+
+//a_team.c
+void MakeAllLivePlayersObservers( void );
+
+//a_cmds.c
+void Cmd_NextMap_f( edict_t * ent );
diff --git a/actionlite/action/a_radio.cpp b/actionlite/action/a_radio.cpp
new file mode 100644
index 0000000..4b44add
--- /dev/null
+++ b/actionlite/action/a_radio.cpp
@@ -0,0 +1,732 @@
+#include "../g_local.h"
+
+void Cmd_Say_f (edict_t * ent, bool team, bool arg0,
+ bool partner_msg);
+
+// Each of the possible radio messages and their length
+struct radio_msg_t
+{
+ char *msg; // the msg name
+ int length; // length in server frames (ie tenths of a second), rounded up
+ int sndIndex;
+};
+
+static radio_msg_t male_radio_msgs[] = {
+ {"1", 6, 0},
+ {"2", 6, 0},
+ {"3", 8, 0},
+ {"4", 7, 0},
+ {"5", 8, 0},
+ {"6", 9, 0},
+ {"7", 8, 0},
+ {"8", 7, 0},
+ {"9", 7, 0},
+ {"10", 6, 0},
+ {"back", 6, 0},
+ {"cover", 7, 0},
+ {"down", 13, 0},
+ {"enemyd", 10, 0},
+ {"enemys", 9, 0},
+ {"forward", 6, 0},
+ {"go", 6, 0},
+ {"im_hit", 7, 0},
+ {"left", 7, 0},
+ {"reportin", 9, 0},
+ {"right", 6, 0},
+ {"taking_f", 22, 0},
+ {"teamdown", 13, 0},
+ {"treport", 12, 0},
+ {"up", 4, 0}
+ //{"END", 0, 0} // end of list delimiter
+};
+
+static radio_msg_t female_radio_msgs[] = {
+ {"1", 5, 0},
+ {"2", 5, 0},
+ {"3", 5, 0},
+ {"4", 5, 0},
+ {"5", 5, 0},
+ {"6", 8, 0},
+ {"7", 7, 0},
+ {"8", 5, 0},
+ {"9", 5, 0},
+ {"10", 5, 0},
+ {"back", 6, 0},
+ {"cover", 5, 0},
+ {"down", 6, 0},
+ {"enemyd", 9, 0},
+ {"enemys", 9, 0},
+ {"forward", 8, 0},
+ {"go", 6, 0},
+ {"im_hit", 7, 0},
+ {"left", 8, 0},
+ {"reportin", 9, 0},
+ {"right", 5, 0},
+ {"taking_f", 22, 0},
+ {"teamdown", 10, 0},
+ {"treport", 12, 0},
+ {"up", 6, 0}
+ //{"END", 0, 0}, // end of list delimiter
+};
+
+static const int numMaleSnds = ( sizeof( male_radio_msgs ) / sizeof( male_radio_msgs[0] ) );
+static const int numFemaleSnds = ( sizeof( female_radio_msgs ) / sizeof( female_radio_msgs[0] ) );
+
+#define RADIO_MALE_DIR "radio/male/"
+#define RADIO_FEMALE_DIR "radio/female/"
+#define RADIO_CLICK 0
+#define RADIO_DEATH_MALE 1
+#define RADIO_DEATH_FEMALE 2
+
+radio_msg_t globalRadio[] = {
+ {"radio/click.wav", 2, 0},
+ {"radio/male/rdeath.wav", 27, 0},
+ {"radio/female/rdeath.wav", 30, 0}
+};
+
+void PrecacheRadioSounds ()
+{
+ int i;
+ char path[MAX_QPATH];
+
+ globalRadio[RADIO_CLICK].sndIndex = gi.soundindex(globalRadio[RADIO_CLICK].msg);
+ globalRadio[RADIO_DEATH_MALE].sndIndex = gi.soundindex(globalRadio[RADIO_DEATH_MALE].msg);
+ globalRadio[RADIO_DEATH_FEMALE].sndIndex = gi.soundindex(globalRadio[RADIO_DEATH_FEMALE].msg);
+
+ //male
+ for(i = 0; i < numMaleSnds; i++)
+ {
+ snprintf(path, sizeof(path), "%s%s.wav", RADIO_MALE_DIR, male_radio_msgs[i].msg);
+ male_radio_msgs[i].sndIndex = gi.soundindex(path);
+ }
+
+ //female
+ for(i = 0; i < numFemaleSnds; i++)
+ {
+ snprintf(path, sizeof(path), "%s%s.wav", RADIO_FEMALE_DIR, female_radio_msgs[i].msg);
+ female_radio_msgs[i].sndIndex = gi.soundindex(path);
+ }
+}
+
+static void DeleteRadioQueueEntry( radio_t *radio, int entry_num )
+{
+ int i;
+
+ if (radio->queue_size <= entry_num)
+ {
+ gi.Com_PrintFmt("DeleteRadioQueueEntry: attempt to delete out of range queue entry: %i\n", entry_num);
+ return;
+ }
+
+ for (i = entry_num + 1; i < radio->queue_size; i++)
+ {
+ memcpy(&radio->queue[i - 1], &radio->queue[i], sizeof(radio_queue_entry_t));
+ }
+
+ radio->queue_size--;
+}
+
+// RadioThink should be called once on each player per server frame.
+void RadioThink (edict_t * ent)
+{
+ radio_t *radio = &ent->client->resp.radio;
+
+ // Try to clean things up, a bit....
+ if (radio->partner)
+ {
+ if (!radio->partner->inuse ||
+ radio->partner->client->resp.radio.partner != ent)
+ {
+ radio->partner = NULL;
+ }
+ }
+ if (radio->partner_last_offered_to)
+ {
+ if (!radio->partner_last_offered_to->inuse ||
+ radio->partner_last_offered_to->solid == SOLID_NOT)
+ {
+ radio->partner_last_offered_to = NULL;
+ }
+ }
+ if (radio->partner_last_denied_from)
+ {
+ if (!radio->partner_last_denied_from->inuse ||
+ radio->partner_last_denied_from->solid == SOLID_NOT)
+ {
+ radio->partner_last_denied_from = NULL;
+ }
+ }
+ // ................................
+
+ if (radio->power_off)
+ {
+ radio->queue_size = 0;
+ return;
+ }
+
+ if (radio->delay > 0)
+ {
+ radio->delay--;
+ if (radio->delay)
+ return;
+ }
+
+
+ if (radio->queue_size)
+ {
+ edict_t *from;
+ int check;
+
+ from = radio->queue[0].from_player;
+
+ if (!radio->queue[0].click && (!from->inuse || !IS_ALIVE(from)))
+ {
+ if (radio->queue[0].from_gender)
+ {
+ radio->queue[0].sndIndex = globalRadio[RADIO_DEATH_FEMALE].sndIndex;
+ radio->queue[0].length = globalRadio[RADIO_DEATH_FEMALE].length;
+ }
+ else
+ {
+ radio->queue[0].sndIndex = globalRadio[RADIO_DEATH_MALE].sndIndex;
+ radio->queue[0].length = globalRadio[RADIO_DEATH_MALE].length;
+ }
+
+ for (check = 1; check < radio->queue_size; check++)
+ {
+ if (!radio->queue[check].click && radio->queue[check].from_player == from)
+ {
+ DeleteRadioQueueEntry( radio, check );
+ check--;
+ }
+ }
+ }
+
+ // if( ! IsInIgnoreList( ent, from ) )
+ // {
+ // unicastSound( ent, radio->queue[0].sndIndex, 1.0 );
+ // radio->delay = radio->queue[0].length;
+ // }
+ DeleteRadioQueueEntry( radio, 0 ); //We can remove it here?
+ }
+}
+
+static void AppendRadioMsgToQueue( radio_t *radio, int sndIndex, int len, int click, edict_t *from_player )
+{
+ radio_queue_entry_t *newentry;
+
+ if (radio->queue_size >= MAX_RADIO_QUEUE_SIZE)
+ {
+ gi.Com_Print("AppendRadioMsgToQueue: Maximum radio queue size exceeded\n");
+ return;
+ }
+
+ newentry = &radio->queue[radio->queue_size];
+
+ newentry->sndIndex = sndIndex;
+ newentry->from_player = from_player;
+ newentry->from_gender = from_player->client->resp.radio.gender;
+ newentry->length = len;
+ newentry->click = click;
+
+ radio->queue_size++;
+}
+
+static void InsertRadioMsgInQueueBeforeClick( radio_t *radio, int sndIndex, int len, edict_t *from_player )
+{
+ radio_queue_entry_t *newentry;
+
+ if (radio->queue_size >= MAX_RADIO_QUEUE_SIZE)
+ {
+ gi.Com_Print("InsertRadioMsgInQueueBeforeClick: Maximum radio queue size exceeded\n");
+ return;
+ }
+
+ newentry = &radio->queue[radio->queue_size - 1];
+
+ memcpy( &radio->queue[radio->queue_size], newentry, sizeof(radio_queue_entry_t));
+
+ newentry->sndIndex = sndIndex;
+ newentry->from_player = from_player;
+ newentry->from_gender = from_player->client->resp.radio.gender;
+ newentry->length = len;
+ newentry->click = 0;
+
+ radio->queue_size++;
+}
+
+static void AddRadioMsg( radio_t *radio, int sndIndex, int len, edict_t *from_player )
+{
+ if (radio->queue_size == 0)
+ {
+ AppendRadioMsgToQueue( radio, globalRadio[RADIO_CLICK].sndIndex, globalRadio[RADIO_CLICK].length, 1, from_player );
+ AppendRadioMsgToQueue( radio, sndIndex, len, 0, from_player );
+ AppendRadioMsgToQueue( radio, globalRadio[RADIO_CLICK].sndIndex, globalRadio[RADIO_CLICK].length, 1, from_player );
+ }
+ else // we have some msgs in it already...
+ {
+ if (radio->queue_size < MAX_RADIO_QUEUE_SIZE)
+ InsertRadioMsgInQueueBeforeClick( radio, sndIndex, len, from_player );
+ // else ignore the message...
+ }
+}
+
+void RadioBroadcast (edict_t * ent, int partner, const char *msg)
+{
+ int j, i, msg_len, numSnds;
+ edict_t *other;
+ radio_msg_t *radio_msgs;
+ int msg_soundIndex = 0;
+ char msgname_num[8], filteredmsg[48];
+ bool found = false;
+ radio_t *radio;
+
+ if (!IS_ALIVE(ent))
+ return;
+
+ radio = &ent->client->resp.radio;
+ if (radio->power_off)
+ {
+ gi.Center_Print (ent, "Your radio is off!");
+ return;
+ }
+
+ if (partner && radio->partner == NULL)
+ {
+ gi.LocClient_Print (ent, PRINT_HIGH, "You don't have a partner.\n");
+ return;
+ }
+
+ if (radio->gender)
+ {
+ radio_msgs = female_radio_msgs;
+ numSnds = numFemaleSnds;
+ }
+ else
+ {
+ radio_msgs = male_radio_msgs;
+ numSnds = numMaleSnds;
+ }
+
+ i = found = 0;
+ msg_len = 0;
+
+ Q_strlcpy(filteredmsg, msg, sizeof(filteredmsg));
+
+ for(i = 0; i < numSnds; i++)
+ {
+ if (!strcmp(radio_msgs[i].msg, filteredmsg))
+ {
+ found = true;
+ msg_soundIndex = radio_msgs[i].sndIndex;
+ msg_len = radio_msgs[i].length;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ gi.LocCenter_Print (ent, "'%s' is not a valid radio message", filteredmsg);
+ return;
+ }
+
+ if (radiolog->value)
+ {
+ gi.LocClient_Print (NULL, PRINT_CHAT, "[%s RADIO] %s: %s\n",
+ partner ? "PARTNER" : "TEAM", ent->client->pers.netname, filteredmsg);
+ }
+
+ //TempFile BEGIN
+ if (strcmp (filteredmsg, "enemyd") == 0)
+ {
+ if (ent->client->radio_num_kills > 1 && ent->client->radio_num_kills <= 10)
+ {
+ // If we are reporting enemy down, add the number of kills.
+ sprintf( msgname_num, "%i", ent->client->radio_num_kills );
+ ent->client->radio_num_kills = 0; // prevent from getting into an endless loop
+
+ RadioBroadcast(ent, partner, msgname_num); // Now THAT'S recursion! =)
+ }
+ ent->client->radio_num_kills = 0;
+ }
+//TempFile END
+ //AQ2:TNG Slicer
+ if (radio_repeat->value)
+ { //SLIC2 Optimization
+ if (CheckForRepeat (ent, i) == false)
+ return;
+ }
+
+ if (radio_max->value)
+ {
+ if (CheckForFlood (ent) == false)
+ return;
+ }
+
+
+ //AQ2:TNG END
+ for (j = 1; j <= game.maxclients; j++)
+ {
+ other = &g_edicts[j];
+ if (!other->inuse)
+ continue;
+ if (!other->client)
+ continue;
+ if (!OnSameTeam(ent, other))
+ continue;
+ if (partner && other != radio->partner)
+ continue;
+ AddRadioMsg( &other->client->resp.radio, msg_soundIndex, msg_len, ent );
+ }
+}
+
+void Cmd_Radio_f (edict_t * ent)
+{
+ RadioBroadcast(ent, ent->client->resp.radio.partner_mode, gi.args());
+}
+
+void Cmd_Radiopartner_f (edict_t * ent)
+{
+ RadioBroadcast(ent, 1, gi.args());
+}
+
+void Cmd_Radioteam_f (edict_t * ent)
+{
+ RadioBroadcast(ent, 0, gi.args());
+}
+
+void Cmd_Radiogender_f (edict_t * ent)
+{
+ const char *arg;
+ radio_t *radio;
+
+ radio = &ent->client->resp.radio;
+ arg = gi.args();
+ if (arg == NULL || !*arg)
+ {
+ if (radio->gender)
+ gi.LocClient_Print (ent, PRINT_HIGH, "Radio gender currently set to female\n");
+ else
+ gi.LocClient_Print (ent, PRINT_HIGH, "Radio gender currently set to male\n");
+ return;
+ }
+
+ if (!strcmp(arg, "male"))
+ {
+ gi.LocClient_Print (ent, PRINT_HIGH, "Radio gender set to male\n");
+ radio->gender = 0;
+ }
+ else if (!strcmp(arg, "female"))
+ {
+ gi.LocClient_Print (ent, PRINT_HIGH, "Radio gender set to female\n");
+ radio->gender = 1;
+ }
+ else
+ {
+ gi.LocClient_Print (ent, PRINT_HIGH, "Invalid gender selection, try 'male' or 'female'\n");
+ }
+}
+
+void Cmd_Radio_power_f (edict_t * ent)
+{
+ radio_t *radio;
+
+ radio = &ent->client->resp.radio;
+
+ radio->power_off = !radio->power_off;
+
+
+
+ gi.LocCenter_Print(ent, "Radio switched %s", (radio->power_off) ? "off" : "on");
+
+ gi.sound(ent, CHAN_VOICE, globalRadio[RADIO_CLICK].sndIndex, 1, ATTN_NORM, 1.0);
+ //unicastSound(ent, globalRadio[RADIO_CLICK].sndIndex, 1.0);
+}
+
+void Cmd_Channel_f (edict_t * ent)
+{
+ radio_t *radio;
+
+ radio = &ent->client->resp.radio;
+
+ radio->partner_mode = !radio->partner_mode;
+ if (radio->partner_mode)
+ {
+ gi.Center_Print (ent, "Channel set to 1, partner channel");
+ }
+ else
+ {
+ gi.Center_Print (ent, "Channel set to 0, team channel");
+ }
+}
+
+edict_t *DetermineViewedPlayer(edict_t *ent, bool teammate);
+
+void Cmd_Partner_f (edict_t * ent)
+{
+ edict_t *target;
+ const char *genderstr;
+ radio_t *radio, *tRadio;
+
+ if (!IS_ALIVE(ent))
+ return;
+
+ radio = &ent->client->resp.radio;
+ if (radio->partner) {
+
+ if (radio->partner->inuse) {
+
+ gi.LocCenter_Print( ent, "You already have a partner, %s", radio->partner->client->pers.netname );
+
+ return;
+
+ }
+
+ // just in case RadioThink hasn't caught it yet... avoid any problems
+
+ radio->partner = NULL;
+
+ }
+
+ target = DetermineViewedPlayer(ent, true);
+ if (target == NULL) {
+ gi.Center_Print (ent, "No potential partner selected");
+ return;
+ }
+
+ tRadio = &target->client->resp.radio;
+ if (tRadio->partner) {
+ gi.LocCenter_Print (ent, "%s already has a partner", target->client->pers.netname);
+ return;
+ }
+
+ if (tRadio->partner_last_offered_to == ent &&
+ radio->partner_last_offered_from == target)
+ {
+ gi.LocCenter_Print (ent, "%s is now your partner", target->client->pers.netname);
+ gi.LocCenter_Print (target, "%s is now your partner", ent->client->pers.netname);
+ radio->partner = target;
+ tRadio->partner = ent;
+ radio->partner_last_offered_from = NULL;
+ tRadio->partner_last_offered_to = NULL;
+ return;
+ }
+
+ if (tRadio->partner_last_denied_from == ent)
+ {
+ gi.LocCenter_Print (ent, "%s has already denied you", target->client->pers.netname);
+ return;
+ }
+
+ if (target == radio->partner_last_offered_to)
+ {
+ genderstr = GENDER_STR(target, "him", "her", "it");
+ gi.LocCenter_Print (ent, "Already awaiting confirmation from %s", genderstr);
+ return;
+ }
+
+ genderstr = GENDER_STR(ent, "him", "her", "it");
+
+ gi.LocCenter_Print (ent, "Awaiting confirmation from %s", target->client->pers.netname);
+ gi.LocCenter_Print (target,
+ "%s offers to be your partner\n"
+ "To accept:\nView %s and use the 'partner' command\n"
+ "To deny:\nUse the 'deny' command",
+ ent->client->pers.netname, genderstr);
+
+ radio->partner_last_offered_to = target;
+ tRadio->partner_last_offered_from = ent;
+}
+
+void Cmd_Unpartner_f (edict_t * ent)
+{
+ edict_t *target;
+ radio_t *radio;
+
+ radio = &ent->client->resp.radio;
+ if (radio->partner && !radio->partner->inuse)
+ { // just in case RadioThink hasn't caught it yet... avoid any problems
+ radio->partner = NULL;
+ }
+
+ target = radio->partner;
+ if (target == NULL) {
+ gi.Center_Print (ent, "You don't have a partner");
+ return;
+ }
+
+ if (target->client->resp.radio.partner == ent)
+ {
+ gi.LocCenter_Print (target, "%s broke your partnership", ent->client->pers.netname);
+ target->client->resp.radio.partner = NULL;
+ }
+
+ gi.LocCenter_Print (ent, "You broke your partnership with %s", target->client->pers.netname);
+ radio->partner = NULL;
+}
+
+void Cmd_Deny_f (edict_t * ent)
+{
+ edict_t *target;
+ radio_t *radio;
+
+ if (!IS_ALIVE(ent))
+ return;
+
+ radio = &ent->client->resp.radio;
+ target = radio->partner_last_offered_from;
+ if (target && target->inuse)
+ {
+ gi.LocCenter_Print (ent, "You denied %s", target->client->pers.netname);
+ gi.LocCenter_Print (target, "%s has denied you", ent->client->pers.netname);
+ radio->partner_last_denied_from = target;
+
+ radio->partner_last_offered_from = NULL;
+ if (target->client->resp.radio.partner_last_offered_to == ent)
+
+ target->client->resp.radio.partner_last_offered_to = NULL;
+ }
+ else
+ {
+ gi.Center_Print (ent, "No one has offered to be your partner");
+ return;
+ }
+}
+
+void Cmd_Say_partner_f (edict_t * ent)
+{
+ if (ent->client->resp.radio.partner == NULL)
+ {
+ gi.LocClient_Print (ent, PRINT_HIGH, "You don't have a partner.\n");
+ return;
+ }
+
+ Cmd_Say_f (ent, false, false, true);
+}
+
+//SLIC2 Redesigned and optimized these two functions
+
+bool CheckForFlood( edict_t * ent )
+
+{
+
+ radio_t *radio = &ent->client->resp.radio;
+
+ //If he's muted..
+
+ if (radio->rd_mute) {
+
+ if (radio->rd_mute > level.time.seconds()) // Still muted..
+
+ return false;
+
+
+
+ radio->rd_mute = 0; // No longer muted..
+
+ }
+
+ if (!radio->rd_Count) {
+
+ radio->rd_time = level.time.seconds();
+
+ radio->rd_Count++;
+
+ }
+
+ else {
+
+ if (level.time.seconds() - radio->rd_time < (int)(radio_time->value * 10)) {
+
+ if (++radio->rd_Count >= (int)radio_max->value) {
+
+ gi.LocClient_Print( ent, PRINT_HIGH,
+
+ "[RADIO FLOOD PROTECTION]: Flood Detected, you are silenced for %d secs\n", (int)radio_ban->value );
+
+ radio->rd_mute = level.time.seconds() + (int)(radio_ban->value * 10);
+
+ return false;
+
+ }
+
+ }
+
+ else {
+
+ radio->rd_Count = 0;
+
+ }
+
+ }
+
+
+
+ return true;
+
+}
+
+
+
+bool CheckForRepeat( edict_t * ent, int radioCode )
+
+{
+
+ radio_t *radio = &ent->client->resp.radio;
+
+
+
+ //If he's muted..
+
+ if (radio->rd_mute) {
+
+ if (radio->rd_mute > level.time.seconds()) // Still muted..
+
+ return false;
+
+
+
+ radio->rd_mute = 0; // No longer muted..
+
+ }
+
+
+
+ if (radio->rd_lastRadio == radioCode) { //He's trying to repeat it..
+
+ if (level.time.seconds() - radio->rd_repTime < (int)(radio_repeat_time->value * 10)) {
+
+ if (++radio->rd_repCount == (int)radio_repeat->value) { //Busted
+
+ gi.LocClient_Print( ent, PRINT_HIGH, "[RADIO FLOOD PROTECTION]: Repeat Flood Detected, you are silenced for %d secs\n", (int)radio_ban->value );
+
+ radio->rd_mute = level.time.seconds() + (int)(radio_ban->value * 10);
+
+ return false;
+
+ }
+
+ }
+
+ else {
+
+ radio->rd_repCount = 0;
+
+ }
+
+ }
+
+ else {
+
+ radio->rd_lastRadio = radioCode;
+
+ radio->rd_repCount = 0;
+
+ }
+
+ radio->rd_repTime = level.time.seconds();
+
+ return true;
+
+}
+
diff --git a/actionlite/action/a_radio.h b/actionlite/action/a_radio.h
new file mode 100644
index 0000000..ca7a7bd
--- /dev/null
+++ b/actionlite/action/a_radio.h
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// a_radio.h
+//
+// Include file for use with radio stuff
+// -Fireblade
+//
+// $Id: a_radio.h,v 1.4 2004/04/08 23:19:51 slicerdw Exp $
+//
+//-----------------------------------------------------------------------------
+// $Log: a_radio.h,v $
+// Revision 1.4 2004/04/08 23:19:51 slicerdw
+// Optimized some code, added a couple of features and fixed minor bugs
+//
+// Revision 1.3 2001/09/28 13:48:34 ra
+// I ran indent over the sources. All .c and .h files reindented.
+//
+// Revision 1.2 2001/08/15 14:50:48 slicerdw
+// Added Flood protections to Radio & Voice, Fixed the sniper bug AGAIN
+//
+// Revision 1.1.1.1 2001/05/06 17:24:32 igor_rock
+// This is the PG Bund Edition V1.25 with all stuff laying around here...
+//
+//-----------------------------------------------------------------------------
+
+#define MAX_SOUNDFILE_PATH_LEN 32 // max length of a sound file path
+#define MAX_RADIO_MSG_QUEUE_SIZE 4
+#define MAX_RADIO_QUEUE_SIZE 6 // this must be at least 2 greater than the above
+
+typedef struct radio_queue_entry_s
+{
+ int sndIndex;
+ edict_t *from_player;
+ int from_gender; // true if female
+
+ int length;
+ bool click;
+} radio_queue_entry_t;
+
+
+typedef struct radio_s
+{
+ int delay;
+ radio_queue_entry_t queue[MAX_RADIO_QUEUE_SIZE];
+ int queue_size;
+
+ bool gender; // radiogender
+ bool power_off; // radio_power
+
+ // Partners stuff
+ bool partner_mode; // 'radio' command using team or partner
+ edict_t *partner; // current partner
+ edict_t *partner_last_offered_to; // last person I offered a partnership to
+ edict_t *partner_last_offered_from; // last person I received a partnership offer from
+ edict_t *partner_last_denied_from; // last person I denied a partnership offer from
+
+ //Flood & Repeat
+ int rd_mute; //Time to be muted
+ int rd_Count; //Counter for the last msgs in "xx" secs allowed
+ int rd_time; //Frame for the first radio message of the ones to follow
+
+ int rd_lastRadio; //Code of the last radio used
+ int rd_repCount; //Counter for the number of repeated radio msgs
+ int rd_repTime; //Frame for the last repeated radio msg
+} radio_t;
+
+void RadioThink (edict_t *);
+void Cmd_Radio_f (edict_t *);
+void Cmd_Radiogender_f (edict_t *);
+void Cmd_Radio_power_f (edict_t *);
+void Cmd_Radiopartner_f (edict_t *);
+void Cmd_Radioteam_f (edict_t *);
+void Cmd_Channel_f (edict_t *);
+void Cmd_Say_partner_f (edict_t *);
+void Cmd_Partner_f (edict_t *);
+void Cmd_Deny_f (edict_t *);
+void Cmd_Unpartner_f (edict_t *);
+void PrecacheRadioSounds ();
+bool CheckForFlood (edict_t * ent);
+bool CheckForRepeat (edict_t * ent, int radioCode);
diff --git a/actionlite/action/a_team.cpp b/actionlite/action/a_team.cpp
new file mode 100644
index 0000000..1f3999d
--- /dev/null
+++ b/actionlite/action/a_team.cpp
@@ -0,0 +1,2945 @@
+#include "../g_local.h"
+#include "cgf_sfx_glass.h"
+#include "../ctf/g_ctf.h"
+
+
+bool team_game_going = false; // is a team game going right now?
+bool team_round_going = false; // is an actual round of a team game going right now?
+
+int team_round_countdown = 0; // countdown variable for start of a round
+int rulecheckfrequency = 0; // accumulator variable for checking rules every 1.5 secs
+int lights_camera_action = 0; // countdown variable for "lights...camera...action!"
+int timewarning = 0; // countdown variable for "x Minutes left"
+int fragwarning = 0; // countdown variable for "x Frags left"
+int holding_on_tie_check = 0; // when a team "wins", countdown for a bit and wait...
+int current_round_length = 0; // frames that the current team round has lasted
+int round_delay_time = 0; // time gap between round end and new round
+int in_warmup = 0; // if warmup is currently on
+bool teams_changed = false; // Need to update the join menu.
+
+team_t teams[TEAM_TOP];
+int teamCount = 2;
+int gameSettings;
+ctfgame_t ctfgame;
+
+#define MAX_SPAWNS 512 // max DM spawn points supported
+
+edict_t *potential_spawns[MAX_SPAWNS];
+int num_potential_spawns;
+edict_t *teamplay_spawns[MAX_TEAMS];
+trace_t trace_t_temp; // used by our trace replace macro in ax_team.h
+
+//
+int NS_num_used_farteamplay_spawns[MAX_TEAMS];
+int NS_num_potential_spawns[MAX_TEAMS];
+edict_t *NS_potential_spawns[MAX_TEAMS][MAX_SPAWNS];
+edict_t *NS_used_farteamplay_spawns[MAX_TEAMS][MAX_SPAWNS];
+int NS_randteam;
+//
+
+void CreditsMenu (edict_t * ent, pmenu_t * p);
+static transparent_list_t transparentList[MAX_CLIENTS];
+static size_t transparentEntryCount = 0;
+transparent_list_t *transparent_list = NULL;
+static transparent_list_t *transparentlistFree = NULL;
+
+void InitTransparentList( void )
+{
+ transparent_list = NULL;
+ transparentlistFree = NULL;
+ transparentEntryCount = 0;
+}
+
+void AddToTransparentList( edict_t *ent )
+{
+ transparent_list_t *entry;
+
+ if (transparentlistFree) {
+ entry = transparentlistFree;
+ transparentlistFree = entry->next;
+ }
+ else if (transparentEntryCount < MAX_CLIENTS) {
+ entry = &transparentList[transparentEntryCount++];
+ }
+ else {
+ return;
+ }
+
+ entry->ent = ent;
+ entry->next = transparent_list;
+ transparent_list = entry;
+}
+
+void RemoveFromTransparentList( edict_t *ent )
+{
+ transparent_list_t *entry, **back;
+
+ back = &transparent_list;
+
+ for (entry = *back; entry; entry = *back) {
+ if (entry->ent == ent) {
+ *back = entry->next;
+ entry->next = transparentlistFree;
+ transparentlistFree = entry;
+ return;
+ }
+
+ back = &entry->next;
+ }
+}
+
+void TransparentListSet( solid_t solid_type )
+{
+ transparent_list_t *entry;
+
+ for (entry = transparent_list; entry; entry = entry->next) {
+ if (entry->ent->solid == solid_type)
+ continue;
+
+ entry->ent->solid = solid_type;
+ gi.linkentity( entry->ent );
+ }
+}
+
+bool OnTransparentList( const edict_t *ent )
+{
+ const transparent_list_t *entry;
+
+ for( entry = transparent_list; entry; entry = entry->next )
+ {
+ if( entry->ent == ent )
+ return true;
+ }
+
+ return false;
+}
+
+void ReprintMOTD (edict_t * ent, pmenu_t * p)
+{
+ PMenu_Close (ent);
+ PrintMOTD (ent);
+}
+
+void PrintMatchRules ()
+{
+ char rulesmsg[256];
+
+ if (!deathmatch->value) {
+ if (teamCount == TEAM2) {
+ snprintf( rulesmsg, sizeof( rulesmsg ), "%s versus: %s\n\nFrag the other team!\n",
+ teams[TEAM1].name, teams[TEAM2].name );
+ } else if (teamCount == TEAM3) {
+ snprintf( rulesmsg, sizeof( rulesmsg ), "%s versus %s versus %s\n\nFrag the other team!\n",
+ teams[TEAM1].name, teams[TEAM2].name, teams[TEAM3].name );
+ }
+ } else {
+ // If nothing else matches, just say glhf
+ snprintf( rulesmsg, sizeof( rulesmsg ), "Frag 'em all! Good luck and have fun!\n");
+ }
+ CenterPrintAll(rulesmsg);
+}
+
+void JoinTeamAuto (edict_t * ent, pmenu_t * p)
+{
+ int i, team = TEAM1, num1 = 0, num2 = 0, num3 = 0, score1, score2, score3;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ if (!g_edicts[i + 1].inuse)
+ continue;
+ if (game.clients[i].resp.team == TEAM1)
+ num1++;
+ else if (game.clients[i].resp.team == TEAM2)
+ num2++;
+ else if (game.clients[i].resp.team == TEAM3)
+ num3++;
+ }
+
+ score1 = teams[TEAM1].score;
+ score2 = teams[TEAM2].score;
+ score3 = teams[TEAM3].score;
+
+ // if(ctf->value) {
+ // CTFCalcScores();
+ // GetCTFScores(&score1, &score2);
+ // }
+
+ /* there are many different things to consider when selecting a team */
+ if (num1 > num2 || (num1 == num2 && score1 > score2))
+ team = TEAM2;
+
+ if (teamCount == 3)
+ {
+ if (team == TEAM1)
+ {
+ if (num1 > num3 || (num1 == num3 && score1 > score3))
+ team = TEAM3;
+ }
+ else
+ {
+ if (num2 > num3 || (num2 == num3 && score2 > score3))
+ team = TEAM3;
+ }
+ }
+
+ JoinTeam(ent, team, 0);
+}
+
+void JoinTeam1 (edict_t * ent, pmenuhnd_t * p)
+{
+ JoinTeam(ent, TEAM1, 0);
+}
+
+void JoinTeam2 (edict_t * ent, pmenuhnd_t * p)
+{
+ JoinTeam(ent, TEAM2, 0);
+}
+
+void JoinTeam3 (edict_t * ent, pmenuhnd_t * p)
+{
+ if (teamCount == 3)
+ JoinTeam(ent, TEAM3, 0);
+}
+
+void LeaveTeams (edict_t * ent, pmenuhnd_t * p)
+{
+ LeaveTeam(ent);
+ PMenu_Close(ent);
+ OpenJoinMenu(ent);
+}
+
+void SelectWeapon2(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_MP5);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/mp5slide.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon3(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_M3);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/m3in.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon4(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_HANDCANNON);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/cclose.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon5(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_SNIPER);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/ssgbolt.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon6(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_M4);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/m4a1slide.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon0(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_KNIFE);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/swish.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectWeapon9(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_DUALMK23);
+ PMenu_Close(ent);
+ OpenItemMenu(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("weapons/mk23slide.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem1(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_VEST);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/veston.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem2(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_LASERSIGHT);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/lasersight.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem3(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_SLIPPERS);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/veston.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem4(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_QUIET);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/screw.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem5(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_BANDOLIER);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/veston.wav"), 1, ATTN_NORM, 0);
+}
+
+void SelectItem6(edict_t *ent, pmenuhnd_t *p)
+{
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_HELM);
+ PMenu_Close(ent);
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/veston.wav"), 1, ATTN_NORM, 0);
+}
+
+// newrand returns n, where 0 >= n < top
+int newrand (int top)
+{
+ return (int) (random () * top);
+}
+
+
+void CreditsReturnToMain (edict_t * ent, pmenuhnd_t * p)
+{
+ PMenu_Close (ent);
+ if (teamplay->value) {
+ OpenJoinMenu (ent);
+ }
+}
+
+//PG BUND BEGIN
+void DoAGoodie (edict_t * ent, pmenuhnd_t * p)
+{
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("boss3/bs3idle1.wav"), 1, ATTN_NORM, 0);
+}
+//PG BUND END
+
+// AQ2:TNG - Igor adding the Rock-Sound ;-)
+void RockClan (edict_t * ent, pmenuhnd_t * p)
+{
+ gi.LocClient_Print (ent, PRINT_HIGH, "Let's Rock! http://www.rock-clan.de/\n");
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("user/letsrock.wav"), 1, ATTN_NORM, 0);
+}
+// AQ2:TNG - End Rock-Sound
+
+// AQ2:TNG Deathwatch - Just for slicer :)
+void SlicersCat (edict_t * ent, pmenuhnd_t * p)
+{
+ gi.LocClient_Print (ent, PRINT_HIGH, "sLiCeR [dW] couldn't have done it without his cat!\n");
+ //PG BUND
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("makron/laf4.wav"), 1, ATTN_NORM, 0);
+ //stuffcmd (ent, "play makron/laf4.wav");
+}
+// AQ2:TNG End
+
+// AQ2:TNG Deathwatch - Just for QNI ppl
+void QuakeNigguhz (edict_t * ent, pmenuhnd_t * p)
+{
+ gi.LocClient_Print (ent, PRINT_HIGH, "For all the homies!\n");
+ //PG BUND
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("world/xian1.wav"), 1, ATTN_NORM, 0);
+ //stuffcmd (ent, "play world/xian1.wav");
+}
+
+// AQ2:TNG Deathwatch - Editing all menus to show the correct credits, version, names, locations, urls, etc
+pmenu_t creditsmenu[] = {
+ {"*" TNG_TITLE, PMENU_ALIGN_CENTER, NULL},
+ {"\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F", PMENU_ALIGN_CENTER, NULL},
+ {"*Design Team", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"Deathwatch", PMENU_ALIGN_LEFT, DoAGoodie},
+ {"Elviz", PMENU_ALIGN_LEFT, DoAGoodie},
+ {"Freud [QNI]", PMENU_ALIGN_LEFT, QuakeNigguhz},
+ {"Igor[Rock]", PMENU_ALIGN_LEFT, RockClan},
+ {"JBravo[QNI]", PMENU_ALIGN_LEFT, QuakeNigguhz},
+ {"sLiCeR [dW]", PMENU_ALIGN_LEFT, SlicersCat},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"*Credits", PMENU_ALIGN_LEFT, NULL},
+ {"(in no particular order)", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"Clan Rock, dW, QNI & DP,", PMENU_ALIGN_LEFT, NULL},
+ {"Kobra, Zarjazz,", PMENU_ALIGN_LEFT, NULL},
+ {"Killerbee, Rookie[Rock],", PMENU_ALIGN_LEFT, NULL},
+ {"PG Bund[Rock], Mort,", PMENU_ALIGN_LEFT, NULL},
+ {"ICE-M, Palmtree,", PMENU_ALIGN_LEFT, NULL},
+ {"Tempfile, Blackmonk,", PMENU_ALIGN_LEFT, NULL},
+ {"Dome, Papst, Apr/ Maniac", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"Return to main menu", PMENU_ALIGN_LEFT, CreditsReturnToMain},
+ {"TAB to exit menu", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"v" VERSION, PMENU_ALIGN_RIGHT, NULL},
+//PG BUND END
+};
+
+pmenu_t weapmenu[] = {
+ {"*" TNG_TITLE, PMENU_ALIGN_CENTER, NULL},
+ {"\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F", PMENU_ALIGN_CENTER, NULL},
+ {"Select your Weapon", PMENU_ALIGN_CENTER, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG - Igor adding wp_flags
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "MP5/10 Submachinegun", SelectWeapon2
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "M3 Super90 Assault Shotgun", SelectWeapon3
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Handcannon", SelectWeapon4
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "SSG 3000 Sniper Rifle", SelectWeapon5
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "M4 Assault Rifle", SelectWeapon6
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Combat Knives", SelectWeapon0
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Akimbo Pistols", SelectWeapon9
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG End adding wp_flags
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG - Slicer: changing this
+ //{"Leave Team", PMENU_ALIGN_LEFT, NULL, LeaveTeams},
+ {"Return to Main Menu", PMENU_ALIGN_LEFT, CreditsReturnToMain},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG END
+ {"Use arrows to move cursor", PMENU_ALIGN_LEFT, NULL},
+ {"ENTER to select", PMENU_ALIGN_LEFT, NULL},
+ {"TAB to exit menu", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"v" VERSION, PMENU_ALIGN_RIGHT, NULL},
+};
+
+pmenu_t itemmenu[] = {
+ {"*" TNG_TITLE, PMENU_ALIGN_CENTER, NULL},
+ {"\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F", PMENU_ALIGN_CENTER, NULL},
+ {"Select your Item", PMENU_ALIGN_CENTER, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG Igor adding itm_flags
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Kevlar Vest", SelectItem1
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Laser Sight", SelectItem2
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Stealth Slippers", SelectItem3
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Silencer", SelectItem4
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Bandolier", SelectItem5
+ {NULL, PMENU_ALIGN_LEFT, NULL}, // "Kevlar Helmet", SelectItem6
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG end adding itm_flags
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"Use arrows to move cursor", PMENU_ALIGN_LEFT, NULL},
+ {"ENTER to select", PMENU_ALIGN_LEFT, NULL},
+ {"TAB to exit menu", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"v" VERSION, PMENU_ALIGN_RIGHT, NULL},
+};
+
+//AQ2:TNG - slicer
+void VotingMenu (edict_t * ent, pmenu_t * p)
+{
+ PMenu_Close (ent);
+ //vShowMenu (ent, "");
+}
+//AQ2:TNG END
+
+pmenu_t joinmenu[] = {
+ {"*" TNG_TITLE, PMENU_ALIGN_CENTER, NULL},
+ {"\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F", PMENU_ALIGN_CENTER, NULL},
+ {NULL /* lvl name */ , PMENU_ALIGN_CENTER, NULL},
+ {NULL, PMENU_ALIGN_CENTER, NULL},
+ {NULL /* team 1 */ , PMENU_ALIGN_LEFT, JoinTeam1},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {NULL /* team 2 */ , PMENU_ALIGN_LEFT, JoinTeam2},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {NULL /* team 3 */ , PMENU_ALIGN_LEFT, JoinTeam3},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {NULL /* auto-join */ , PMENU_ALIGN_LEFT, JoinTeamAuto},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ //AQ2:TNG - Slicer
+ {"Voting & Ignoring Menus", PMENU_ALIGN_LEFT, VotingMenu},
+ //AQ2:TNG END
+ {"MOTD", PMENU_ALIGN_LEFT, ReprintMOTD},
+ {"Credits", PMENU_ALIGN_LEFT, CreditsMenu},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"Use arrows to move cursor", PMENU_ALIGN_LEFT, NULL},
+ {"ENTER to select", PMENU_ALIGN_LEFT, NULL},
+ {"TAB to exit menu", PMENU_ALIGN_LEFT, NULL},
+ {NULL, PMENU_ALIGN_LEFT, NULL},
+ {"v" VERSION, PMENU_ALIGN_RIGHT, NULL},
+};
+// AQ2:TNG End
+
+void CreditsMenu (edict_t * ent, pmenu_t * p)
+{
+ PMenu_Close (ent);
+ PMenu_Open (ent, creditsmenu, 4, sizeof (creditsmenu) / sizeof (pmenu_t));
+ gi.sound(ent, gi.soundindex("world/elv.wav"), 1, ATTN_NORM, 1.0);
+}
+
+void killPlayer( edict_t *ent, bool suicidePunish )
+{
+ if (!IS_ALIVE(ent))
+ return;
+
+ if (suicidePunish)
+ {
+ edict_t *attacker = ent->client->attacker;
+ if (attacker && attacker != ent && attacker->client)
+ {
+ char deathmsg[128];
+ snprintf( deathmsg, sizeof( deathmsg ), "%s ph34rs %s so much %s committed suicide! :)\n",
+ ent->client->pers.netname, attacker->client->pers.netname,
+ ent->client->pers.gender ? "she" : "he");
+
+ PrintDeathMessage( deathmsg, ent );
+
+ if (team_round_going || !OnSameTeam( ent, ent->client->attacker )) {
+ Add_Frag( ent->client->attacker, MOD_SUICIDE );
+ Subtract_Frag( ent );
+ Add_Death( ent, true );
+ }
+ }
+ }
+
+ ent->flags &= ~FL_GODMODE;
+ ent->health = 0;
+ meansOfDeath = MOD_SUICIDE;
+ player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, true });
+ ent->deadflag = true;
+}
+
+char *TeamName (int team)
+{
+ if (team >= TEAM1 && team <= TEAM3)
+ return teams[team].name;
+ else
+ return "None";
+}
+
+void AssignSkin (edict_t * ent, const char *s, bool nickChanged)
+{
+ int playernum = ent - g_edicts - 1;
+ char *p;
+ char t[MAX_SKINLEN], skin[64] = "\0";
+ const char *default_skin = "male/grunt";
+
+ switch (ent->client->resp.team)
+ {
+ case TEAM1:
+ case TEAM2:
+ case TEAM3:
+ snprintf(skin, sizeof(skin), "%s\\%s", ent->client->pers.netname, teams[ent->client->resp.team].skin);
+ break;
+ default:
+ snprintf(skin, sizeof(skin), "%s\\%s", ent->client->pers.netname, (teamplay->value ? default_skin : s));
+ break;
+ }
+
+ gi.configstring(CS_PLAYERSKINS + playernum, skin);
+}
+
+/*
+==============
+TP_GetTeamFromArgs
+==============
+*/
+int TP_GetTeamFromArg(const char *name)
+{
+ int i;
+
+ if (!name || !*name)
+ return -1;
+
+ if (!name[1])
+ {
+ i = Q_tolower(name[0]);
+ if (i == '1' || i == 'a')
+ return TEAM1;
+
+ if (i == '2' || i == 'b')
+ return TEAM2;
+
+ if (teamCount > 2 && (i == '3' || i == 'c'))
+ return TEAM3;
+
+ if (i == '0' || i == 's')
+ return NOTEAM;
+ }
+
+ for (i = TEAM1; i <= teamCount; i++) {
+ if (!strcmp(name, teams[i].name))
+ return i;
+ }
+
+ if (!strcmp(name, "none") || !strcmp(name, "spec"))
+ return NOTEAM;
+
+ if (ctf->value)
+ {
+ if (!strcmp(name, "red"))
+ return TEAM1;
+ if (!strcmp(name, "blue"))
+ return TEAM2;
+ }
+
+ return -1;
+}
+
+void Team_f (edict_t * ent)
+{
+ char *t;
+ int desired_team = NOTEAM;
+ char team[24];
+
+ if (!teamplay->value)
+ return;
+
+ strncpy(team, gi.args(), sizeof(team));
+ t = team;
+ // t = gi.args ();
+ if (!*t)
+ {
+ if (ctf->value)
+ gi.LocClient_Print(ent, PRINT_HIGH, "You are on %s.\n", CTFTeamName(ent->client->resp.team));
+ else
+ gi.LocClient_Print(ent, PRINT_HIGH, "You are on %s.\n", TeamName(ent->client->resp.team));
+
+ return;
+ }
+
+ if( (ent->client->resp.joined_team > 0) && (level.realFramenum - ent->client->resp.joined_team < 5 * HZ) )
+ {
+ gi.LocClient_Print(ent, PRINT_HIGH, "You must wait 5 seconds before changing teams again.\n");
+ return;
+ }
+
+ desired_team = TP_GetTeamFromArg(t);
+ if (desired_team == -1) {
+ gi.LocClient_Print(ent, PRINT_HIGH, "Unknown team '%s'.\n", t);
+ return;
+ }
+
+ if (desired_team == NOTEAM)
+ {
+ if (ent->client->resp.team == NOTEAM)
+ gi.LocClient_Print(ent, PRINT_HIGH, "You're not on a team.\n");
+ else
+ LeaveTeam(ent);
+
+ return;
+ }
+
+ if (ent->client->resp.team == desired_team) {
+ gi.LocClient_Print(ent, PRINT_HIGH, "You are already on %s.\n", TeamName(ent->client->resp.team));
+ return;
+ }
+
+ JoinTeam(ent, desired_team, 1);
+}
+
+void JoinTeam (edict_t * ent, int desired_team, int skip_menuclose)
+{
+ char *s;
+ const char *a;
+ int oldTeam;
+
+ if (!skip_menuclose)
+ PMenu_Close (ent);
+
+ oldTeam = ent->client->resp.team;
+ if (oldTeam == desired_team)
+ return;
+
+ if(eventeams->value && desired_team != NOTEAM) {
+ if(!IsAllowedToJoin(ent, desired_team)) {
+ gi.LocCenter_Print(ent, "Cannot join %s (has too many players)", TeamName(desired_team));
+ return;
+ }
+ }
+
+ a = (oldTeam == NOTEAM) ? "joined" : "changed to";
+
+ ent->client->resp.team = desired_team;
+ gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", s, sizeof(s));
+ AssignSkin(ent, s, false);
+
+ ent->flags &= ~FL_GODMODE;
+ killPlayer(ent, true);
+
+ gi.LocBroadcast_Print (PRINT_HIGH, "%s %s %s.\n", ent->client->pers.netname, a, TeamName(desired_team));
+
+ ent->client->resp.joined_team = level.time.milliseconds();
+
+ // if (oldTeam == NOTEAM || desired_team == NOTEAM) {
+ // G_UpdatePlayerStatusbar(ent, 1);
+ // }
+
+ if (level.intermissiontime)
+ return;
+
+ if (!(gameSettings & GS_ROUNDBASED) && team_round_going && ent->inuse && ent->client->resp.team)
+ {
+ PutClientInServer (ent);
+ AddToTransparentList (ent);
+ }
+
+ //AQ2:TNG END
+ if (!skip_menuclose && (gameSettings & GS_WEAPONCHOOSE))
+ OpenWeaponMenu(ent);
+
+ teams_changed = true;
+}
+
+void LeaveTeam (edict_t * ent)
+{
+ const char *genderstr;
+
+ if (ent->client->resp.team == NOTEAM)
+ return;
+
+ killPlayer(ent, true);
+
+ genderstr = GENDER_STR(ent, "his", "her", "its");
+
+ gi.LocBroadcast_Print (PRINT_HIGH, "%s left %s team.\n", ent->client->pers.netname, genderstr);
+
+ ent->client->resp.joined_team = 0;
+ ent->client->resp.team = NOTEAM;
+ //G_UpdatePlayerStatusbar(ent, 1);
+
+#ifdef AQTION_EXTENSION
+ HUD_SetType(ent, 1);
+#endif
+
+ teams_changed = true;
+}
+
+void ReturnToMain (edict_t * ent, pmenu_t * p)
+{
+ PMenu_Close (ent);
+ OpenJoinMenu (ent);
+}
+
+const char *menu_itemnames[WEAPON_MAX+ITEM_MAX] = {
+ "",
+ MK23_NAME,
+ MP5_NAME,
+ M4_NAME,
+ M3_NAME,
+ HC_NAME,
+ SNIPER_NAME,
+ DUAL_NAME,
+ KNIFE_NAME,
+ GRENADE_NAME,
+ SIL_NAME,
+ SLIP_NAME,
+ BAND_NAME,
+ KEV_NAME,
+ LASER_NAME,
+ HELM_NAME,
+};
+
+
+typedef struct menuentry_s
+{
+ int itemNum;
+ void (*SelectFunc) (edict_t * ent, struct pmenu_s * entry);
+} menuentry_t;
+
+void OpenItemMenu (edict_t * ent)
+{
+ menuentry_t *menuEntry, menu_items[] = {
+ { IT_ITEM_VEST, SelectItem1 },
+ { IT_ITEM_LASERSIGHT, SelectItem2 },
+ { IT_ITEM_SLIPPERS, SelectItem3 },
+ { IT_ITEM_SLIPPERS, SelectItem4 },
+ { IT_ITEM_BANDOLIER, SelectItem5 },
+ { IT_ITEM_HELM, SelectItem6 }
+ };
+ int i, count, pos = 4;
+
+ count = sizeof( menu_items ) / sizeof( menu_items[0] );
+
+ // if ((int)itm_flags->value & ITF_MASK)
+ // {
+ // for (menuEntry = menu_items, i = 0; i < count; i++, menuEntry++) {
+ // if (!ITF_ALLOWED(menuEntry->itemNum))
+ // continue;
+
+ // itemmenu[pos].text = menu_itemnames[menuEntry->itemNum];
+ // itemmenu[pos].SelectFunc = menuEntry->SelectFunc;
+ // pos++;
+ // }
+
+ // if ( pos > 4 )
+ // {
+ // for (; pos < 10; pos++)
+ // {
+ // itemmenu[pos].text = NULL;
+ // itemmenu[pos].SelectFunc = NULL;
+ // }
+
+ // PMenu_Open(ent, itemmenu, 4, sizeof(itemmenu) / sizeof(pmenu_t));
+ // return;
+ // }
+ // }
+
+ PMenu_Close(ent);
+}
+
+void OpenWeaponMenu (edict_t * ent)
+{
+ menuentry_t *menuEntry, menu_items[] = {
+ { IT_WEAPON_MP5, SelectWeapon2 },
+ { IT_WEAPON_M3, SelectWeapon3 },
+ { IT_WEAPON_HANDCANNON, SelectWeapon4 },
+ { IT_WEAPON_SNIPER, SelectWeapon5 },
+ { IT_WEAPON_M4, SelectWeapon6 },
+ { IT_WEAPON_KNIFE, SelectWeapon0 },
+ { IT_WEAPON_DUALMK23, SelectWeapon9 }
+ };
+ int i, count, pos = 4;
+
+ count = sizeof( menu_items ) / sizeof( menu_items[0] );
+
+ // TODO Weapon bans
+ // if ((int)wp_flags->value & WPF_MASK)
+ // {
+ // for (menuEntry = menu_items, i = 0; i < count; i++, menuEntry++) {
+ // // TOD: Work in weapon bans
+ // // if (!WPF_ALLOWED(menuEntry->itemNum))
+ // // continue;
+
+ // weapmenu[pos].text = menu_itemnames[menuEntry->itemNum];
+ // weapmenu[pos].SelectFunc = menuEntry->SelectFunc;
+ // pos++;
+ // }
+
+ // if (pos > 4)
+ // {
+ // for (; pos < 11; pos++)
+ // {
+ // weapmenu[pos].text = NULL;
+ // weapmenu[pos].SelectFunc = NULL;
+ // }
+
+ // PMenu_Open(ent, weapmenu, 4, sizeof(weapmenu) / sizeof(pmenu_t));
+ // return;
+ // }
+ // }
+
+ OpenItemMenu(ent);
+}
+
+// AQ2:TNG Deathwatch - Updated this for the new menu
+void UpdateJoinMenu( void )
+{
+ static char levelname[28];
+ static char team1players[28];
+ static char team2players[28];
+ static char team3players[28];
+ int num1 = 0, num2 = 0, num3 = 0, i;
+
+ if (ctf->value)
+ {
+ joinmenu[4].text = "Join Red Team";
+ joinmenu[4].SelectFunc = JoinTeam1;
+ joinmenu[6].text = "Join Blue Team";
+ joinmenu[6].SelectFunc = JoinTeam2;
+ joinmenu[8].text = NULL;
+ joinmenu[8].SelectFunc = NULL;
+ if (g_teamplay_force_join->string && *g_teamplay_force_join->string)
+ {
+ if (strcmp (g_teamplay_force_join->string, "red") == 0)
+ {
+ joinmenu[6].text = NULL;
+ joinmenu[6].SelectFunc = NULL;
+ }
+ else if (strcmp (g_teamplay_force_join->string, "blue") == 0)
+ {
+ joinmenu[4].text = NULL;
+ joinmenu[4].SelectFunc = NULL;
+ }
+ }
+ }
+ else
+ {
+ joinmenu[4].text = teams[TEAM1].name;
+ joinmenu[4].SelectFunc = JoinTeam1;
+ joinmenu[6].text = teams[TEAM2].name;
+ joinmenu[6].SelectFunc = JoinTeam2;
+ if (teamCount == 3)
+ {
+ joinmenu[8].text = teams[TEAM3].name;
+ joinmenu[8].SelectFunc = JoinTeam3;
+ }
+ else
+ {
+ joinmenu[8].text = NULL;
+ joinmenu[8].SelectFunc = NULL;
+ }
+ }
+ joinmenu[11].text = "Auto-join team";
+ joinmenu[11].SelectFunc = JoinTeamAuto;
+
+ levelname[0] = '*';
+ if (g_edicts[0].message)
+ Q_strlcpy(levelname + 1, g_edicts[0].message, sizeof(levelname) - 1);
+ else
+ Q_strlcpy(levelname + 1, level.mapname, sizeof(levelname) - 1);
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ if (!g_edicts[i + 1].inuse)
+ continue;
+ if (game.clients[i].resp.team == TEAM1)
+ num1++;
+ else if (game.clients[i].resp.team == TEAM2)
+ num2++;
+ else if (game.clients[i].resp.team == TEAM3)
+ num3++;
+ }
+
+ sprintf (team1players, " (%d players)", num1);
+ sprintf (team2players, " (%d players)", num2);
+ sprintf (team3players, " (%d players)", num3);
+
+ joinmenu[2].text = levelname;
+ if (joinmenu[4].text)
+ joinmenu[5].text = team1players;
+ else
+ joinmenu[5].text = NULL;
+ if (joinmenu[6].text)
+ joinmenu[7].text = team2players;
+ else
+ joinmenu[7].text = NULL;
+ if (joinmenu[8].text && (teamCount == 3))
+ joinmenu[9].text = team3players;
+ else
+ joinmenu[9].text = NULL;
+}
+
+// AQ2:TNG END
+
+void OpenJoinMenu (edict_t * ent)
+{
+ UpdateJoinMenu();
+
+ PMenu_Open (ent, joinmenu, 11 /* magic for Auto-join menu item */, sizeof (joinmenu) / sizeof (pmenu_t), nullptr, nullptr);
+}
+
+void gib_die( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point ); // g_misc
+
+void CleanLevel ()
+{
+ int i, base;
+ edict_t *ent;
+ base = 1 + game.maxclients + BODY_QUEUE_SIZE;
+ ent = g_edicts + base;
+ action_weapon_num_t weapNum;
+ action_item_num_t itemNum;
+ action_ammo_num_t ammoNum;
+
+ for (i = base; i < globals.num_edicts; i++, ent++)
+ {
+ if (!ent->classname)
+ continue;
+ switch (weapNum) {
+ case MK23_NUM:
+ case MP5_NUM:
+ case M4_NUM:
+ case M3_NUM:
+ case HC_NUM:
+ case SNIPER_NUM:
+ case DUAL_NUM:
+ case KNIFE_NUM:
+ case GRENADE_NUM:
+ G_FreeEdict( ent );
+ break;
+ }
+ switch (itemNum) {
+ case SIL_NUM:
+ case SLIP_NUM:
+ case BAND_NUM:
+ case KEV_NUM:
+ case LASER_NUM:
+ case HELM_NUM:
+ G_FreeEdict( ent );
+ break;
+ }
+ switch (ammoNum) {
+ case MK23_ANUM:
+ case MP5_ANUM:
+ case M4_ANUM:
+ case SHELL_ANUM:
+ case SNIPER_ANUM:
+ G_FreeEdict( ent );
+ break;
+ }
+
+ if((ent->die == gib_die)
+ || (strcmp( ent->classname, "medkit" ) == 0)
+ || (strcmp( ent->classname, "decal" ) == 0)
+ || (strcmp( ent->classname, "splat" ) == 0)
+ || (strcmp( ent->classname, "shell" ) == 0))
+ G_FreeEdict( ent );
+
+ }
+
+ CleanBodies();
+ // fix glass
+ CGF_SFX_RebuildAllBrokenGlass ();
+}
+
+void MakeAllLivePlayersObservers(void);
+
+void ResetScores (bool playerScores)
+{
+ int i;
+ edict_t *ent;
+
+ team_round_going = team_round_countdown = team_game_going = 0;
+ current_round_length = 0;
+ lights_camera_action = holding_on_tie_check = 0;
+
+ timewarning = fragwarning = 0;
+ //level.pauseFrames = 0;
+ level.matchTime = 0;
+
+ MakeAllLivePlayersObservers();
+
+ for(i = TEAM1; i < TEAM_TOP; i++)
+ {
+ teams[i].score = teams[i].total = 0;
+ teams[i].ready = teams[i].locked = 0;
+ teams[i].pauses_used = teams[i].wantReset = 0;
+ gi.cvar_forceset(teams[i].teamscore->name, "0");
+ }
+
+ ctfgame.team1 = 0;
+ ctfgame.team2 = 0;
+ ctfgame.total1 = 0;
+ ctfgame.total2 = 0;
+ ctfgame.last_flag_capture = 0_ms;
+ ctfgame.last_capture_team = 0;
+ ctfgame.halftime = 0;
+
+ if(!playerScores)
+ return;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = g_edicts + 1 + i;
+ if (!ent->inuse)
+ continue;
+
+ ent->client->resp.score = 0;
+ ent->client->resp.kills = 0;
+ ent->client->resp.damage_dealt = 0;
+ ent->client->resp.streakHS = 0;
+ ent->client->resp.streakKills = 0;
+ ent->client->resp.ctf_caps = 0;
+ ent->client->resp.ctf_capstreak = 0;
+ ent->client->resp.deaths = 0;
+ ent->client->resp.team_kills = 0;
+ ent->client->resp.team_wounds = 0;
+ ent->enemy = NULL;
+ //ResetStats(ent);
+ }
+}
+
+int TeamHasPlayers (int team)
+{
+ int i, players;
+ edict_t *ent;
+
+ players = 0;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+ if (!ent->inuse)
+ continue;
+
+ if (game.clients[i].resp.team == team)
+ players++;
+ }
+
+ return players;
+}
+
+int _numclients( void ); // a_vote.c
+
+bool TeamsReady( void )
+{
+ int i, ready = 0;
+
+ for( i = TEAM1; i <= teamCount; i++ )
+ {
+ if( teams[i].ready )
+ ready ++;
+ else if( TeamHasPlayers(i) )
+ return false;
+ }
+ return (ready >= 2);
+}
+
+bool BothTeamsHavePlayers()
+{
+ int players[TEAM_TOP] = { 0 }, i, teamsWithPlayers;
+ edict_t *ent;
+
+ //AQ2:TNG Slicer Matchmode
+ if (matchmode->value && !TeamsReady())
+ return false;
+ //AQ2:TNG END
+
+ if( ! _numclients() )
+ return false;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+ if (!ent->inuse || game.clients[i].resp.team == NOTEAM)
+ continue;
+ if (!game.clients[i].resp.subteam)
+ players[game.clients[i].resp.team]++;
+ }
+
+ teamsWithPlayers = 0;
+ for (i = TEAM1; i <= teamCount; i++)
+ {
+ if (players[i]) {
+ teamsWithPlayers++;
+ }
+ }
+
+ return (teamsWithPlayers >= 2);
+}
+
+// CheckForWinner: Checks for a winner (or not).
+int CheckForWinner()
+{
+ int players[TEAM_TOP] = { 0 }, i = 0, teamNum = 0, teamsWithPlayers = 0;
+ edict_t *ent;
+
+ if (!(gameSettings & GS_ROUNDBASED)) {
+ return WINNER_NONE;
+ } else {
+ for (i = 0; i < game.maxclients; i++){
+ ent = &g_edicts[1 + i];
+ if (!ent->inuse || ent->solid == SOLID_NOT)
+ continue;
+
+ teamNum = game.clients[i].resp.team;
+ if (teamNum == NOTEAM)
+ continue;
+
+ players[teamNum]++;
+ }
+ teamsWithPlayers = 0;
+ for (i = TEAM1; i <= teamCount; i++){
+ if (players[i]) {
+ teamsWithPlayers++;
+ teamNum = i;
+ }
+ }
+ if (teamsWithPlayers)
+ return (teamsWithPlayers > 1) ? WINNER_NONE : teamNum;
+
+ return WINNER_TIE;
+ }
+ return WINNER_NONE;
+}
+
+// CheckForForcedWinner: A winner is being forced, find who it is.
+int CheckForForcedWinner()
+{
+ int players[TEAM_TOP] = { 0 };
+ int health[TEAM_TOP] = { 0 };
+ int i, teamNum, bestTeam, secondBest;
+ edict_t *ent;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+ if (!ent->inuse || ent->solid == SOLID_NOT)
+ continue;
+ teamNum = game.clients[i].resp.team;
+ if (teamNum == NOTEAM)
+ continue;
+
+ players[teamNum]++;
+ health[teamNum] += ent->health;
+ }
+
+ bestTeam = secondBest = NOTEAM;
+ for (i = TEAM1; i <= teamCount; i++)
+ {
+ if (players[i] < players[bestTeam]) {
+ continue;
+ }
+ if (players[i] > players[bestTeam]) {
+ bestTeam = i;
+ secondBest = NOTEAM;
+ continue;
+ }
+ //Same amound of players, check health
+ if (health[i] < health[bestTeam]) {
+ continue;
+ }
+ if (health[i] > health[bestTeam]) {
+ bestTeam = i;
+ secondBest = NOTEAM;
+ continue;
+ }
+ //Same as bestTeam
+ secondBest = i;
+ }
+
+ if (bestTeam == NOTEAM || secondBest != NOTEAM)
+ return WINNER_TIE;
+
+ return bestTeam;
+}
+
+static void SpawnPlayers(void)
+{
+ int i;
+ edict_t *ent;
+
+ if (gameSettings & GS_ROUNDBASED) {
+ NS_SetupTeamSpawnPoints ();
+ }
+
+ InitTransparentList();
+ for (i = 0, ent = &g_edicts[1]; i < game.maxclients; i++, ent++)
+ {
+ if (!ent->inuse)
+ continue;
+
+ if (!ent->client->resp.team || ent->client->resp.subteam)
+ continue;
+
+ // make sure teamplay spawners always have some weapon, warmup starts only after weapon selected
+ if (!ent->client->pers.chosenWeapon) {
+ // TODO: Work in weapon bans
+ // if (WPF_ALLOWED(IT_WEAPON_MP5)) {
+ // ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_MP5);
+ // } else if (WPF_ALLOWED(IT_WEAPON_MK23)) {
+ // ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_MK23);
+ // } else if (WPF_ALLOWED(IT_WEAPON_KNIFE)) {
+ // ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_KNIFE);
+ // } else {
+ ent->client->pers.chosenWeapon = GetItemByIndex(IT_WEAPON_MK23);
+ }
+
+ if (!ent->client->pers.chosenItem) {
+ ent->client->pers.chosenItem = GetItemByIndex(IT_ITEM_VEST);
+ }
+
+ PutClientInServer(ent);
+ AddToTransparentList(ent);
+ }
+
+ if(matchmode->value && limchasecam->value)
+ {
+ for (i = 0, ent = &g_edicts[1]; i < game.maxclients; i++, ent++)
+ {
+ if (!ent->inuse)
+ continue;
+
+ if (!ent->client->resp.team || !ent->client->resp.subteam)
+ continue;
+
+ ent->client->chase_mode = 0;
+ //NextChaseMode( ent );
+ }
+ }
+}
+
+void RunWarmup ()
+{
+ int i, dead;
+ edict_t *ent;
+
+ if (!warmup->value || (matchmode->value && level.matchTime > 0) || team_round_going || lights_camera_action || (team_round_countdown > 0 && team_round_countdown <= 101))
+ return;
+
+ if (!in_warmup)
+ {
+ in_warmup = 1;
+ InitTransparentList();
+ }
+
+ for (i = 0, ent = &g_edicts[1]; i < game.maxclients; i++, ent++)
+ {
+ if (!ent->inuse)
+ continue;
+
+ if(!ent->client->resp.team || ent->client->resp.subteam)
+ continue;
+
+ if (!ent->client->pers.chosenWeapon || !ent->client->pers.chosenItem)
+ continue;
+
+ if ((!IS_ALIVE(ent) && ent->movetype == MOVETYPE_NOCLIP) && ent->client->latched_buttons & BUTTON_ATTACK)
+ {
+ ent->client->latched_buttons = BUTTON_NONE;
+ PutClientInServer(ent);
+ AddToTransparentList(ent);
+ gi.LocCenter_Print(ent, "WARMUP");
+ }
+ }
+}
+
+void StartRound ()
+{
+ team_round_going = 1;
+ current_round_length = 0;
+}
+
+static void StartLCA(void)
+{
+ if ((gameSettings & (GS_WEAPONCHOOSE|GS_ROUNDBASED)))
+ CleanLevel();
+
+
+ CenterPrintAll ("LIGHTS...");
+ gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, level.snd_lights, 1.0, ATTN_NONE, 0.0);
+ lights_camera_action = 43; // TempFile changed from 41
+
+ SpawnPlayers();
+}
+
+// FindOverlap: Find the first (or next) overlapping player for ent.
+edict_t *FindOverlap (edict_t * ent, edict_t * last_overlap)
+{
+ int i;
+ edict_t *other;
+ vec3_t diff;
+
+ for (i = last_overlap ? last_overlap - g_edicts : 0; i < game.maxclients; i++)
+ {
+ other = &g_edicts[i + 1];
+
+ if (!other->inuse || other->client->resp.team == NOTEAM
+ || other == ent || !IS_ALIVE(other))
+ continue;
+
+ VectorSubtract(ent->s.origin, other->s.origin, diff);
+
+ if (diff[0] >= -33 && diff[0] <= 33 &&
+ diff[1] >= -33 && diff[1] <= 33 && diff[2] >= -65 && diff[2] <= 65)
+ return other;
+ }
+
+ return NULL;
+}
+
+void ContinueLCA ()
+{
+ if (lights_camera_action == 23)
+ {
+ CenterPrintAll("CAMERA...");
+ gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, level.snd_camera , 1.0, ATTN_NONE, 0.0);
+ }
+ else if (lights_camera_action == 3)
+ {
+ CenterPrintAll("ACTION!");
+ gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, level.snd_action, 1.0, ATTN_NONE, 0.0);
+ }
+ else if (lights_camera_action == 1)
+ {
+ StartRound();
+ }
+ lights_camera_action--;
+}
+
+void MakeAllLivePlayersObservers (void)
+{
+ edict_t *ent;
+ int saveteam, i;
+
+ // /* if someone is carrying a flag it will disappear */
+ // if(ctf->value)
+ // CTFResetFlags();
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+ if (!ent->inuse)
+ continue;
+ if(ent->solid == SOLID_NOT && !ent->deadflag)
+ continue;
+
+ saveteam = ent->client->resp.team;
+ ent->client->resp.team = NOTEAM;
+ PutClientInServer(ent);
+ ent->client->resp.team = saveteam;
+ }
+}
+
+// PrintScores: Prints the current score on the console
+void PrintScores (void)
+{
+ if (teamCount == 3) {
+ gi.LocBroadcast_Print (PRINT_HIGH, "Current score is %s: %d to %s: %d to %s: %d\n", TeamName (TEAM1), teams[TEAM1].score, TeamName (TEAM2), teams[TEAM2].score, TeamName (TEAM3), teams[TEAM3].score);
+ } else {
+ gi.LocBroadcast_Print (PRINT_HIGH, "Current score is %s: %d to %s: %d\n", TeamName (TEAM1), teams[TEAM1].score, TeamName (TEAM2), teams[TEAM2].score);
+ }
+}
+
+void ResetPlayers()
+{
+ edict_t *ent;
+ int i;
+
+ for (i = 0; i < game.maxclients; i++) {
+ ent = &g_edicts[1 + i];
+ if (ent->inuse) {
+ PutClientInServer(ent);
+ }
+ }
+}
+
+void SendScores(void)
+{
+ unsigned int mins, secs, gametime = level.matchTime;
+
+ mins = gametime / 60;
+ secs = gametime % 60;
+ if(teamCount == 3) {
+ gi.Broadcast_Print(PRINT_HIGH, "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F\n");
+ gi.Broadcast_Print(PRINT_HIGH, " Team 1 Score - Team 2 Score - Team 3 Score\n");
+ gi.LocBroadcast_Print(PRINT_HIGH, " [%d] [%d] [%d]\n", teams[TEAM1].score, teams[TEAM2].score, teams[TEAM3].score);
+ gi.LocBroadcast_Print(PRINT_HIGH, " Total Played Time: %d:%02d\n", mins, secs);
+ gi.Broadcast_Print(PRINT_HIGH, "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F\n");
+ } else {
+ int team1score = 0, team2score = 0;
+
+ // if(ctf->value) {
+ // GetCTFScores(&team1score, &team2score);
+ // } else {
+ team1score = teams[TEAM1].score;
+ team2score = teams[TEAM2].score;
+ //}
+ gi.Broadcast_Print(PRINT_HIGH, "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F\n");
+ gi.Broadcast_Print(PRINT_HIGH, " Team 1 Score - Team 2 Score\n");
+ gi.LocBroadcast_Print(PRINT_HIGH, " [%d] [%d]\n", team1score, team2score);
+ gi.LocBroadcast_Print(PRINT_HIGH, " Total Played Time: %d:%02d\n", mins, secs);
+ gi.Broadcast_Print(PRINT_HIGH, "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F\n");
+ }
+ gi.Broadcast_Print(PRINT_HIGH, "Match is over, waiting for next map, please vote a new one..\n");
+
+ game.roundNum = 0;
+}
+
+bool CheckTimelimit( void )
+{
+ if (timelimit->value > 0)
+ {
+ if (level.matchTime >= timelimit->value * 60)
+ {
+ int i;
+
+ for (i = TEAM1; i < TEAM_TOP; i++) {
+ teams[i].ready = 0;
+ }
+
+ timewarning = fragwarning = 0;
+
+ // if (matchmode->value) {
+ // SendScores();
+ // team_round_going = team_round_countdown = team_game_going = 0;
+ // MakeAllLivePlayersObservers();
+ // ctfgame.halftime = 0;
+ // } else {
+
+ gi.LocBroadcast_Print( PRINT_HIGH, "Timelimit hit.\n" );
+ if (!(gameSettings & GS_ROUNDBASED))
+ ResetPlayers();
+ EndDMLevel();
+
+ team_round_going = team_round_countdown = team_game_going = 0;
+ level.matchTime = 0;
+
+ return true;
+ }
+
+ // CTF with use_warnings should have the same warnings when the map is ending as it does for halftime (see CTFCheckRules).
+ // Otherwise, use_warnings should warn about 3 minutes and 1 minute left, but only if there aren't round ending warnings.
+ if( use_warnings->value && (ctf->value || ! roundtimelimit->value) )
+ {
+ if( timewarning < 3 && ctf->value && level.matchTime >= timelimit->value * 60 - 10 )
+ {
+ gi.sound( &g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, gi.soundindex("world/10_0.wav"), 1.0, ATTN_NONE, 0.0 );
+ timewarning = 3;
+ }
+ else if( timewarning < 2 && level.matchTime >= (timelimit->value - 1) * 60 )
+ {
+ CenterPrintAll( "1 MINUTE LEFT..." );
+ gi.sound( &g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, gi.soundindex("tng/1_minute.wav"), 1.0, ATTN_NONE, 0.0 );
+ timewarning = 2;
+ }
+ else if( timewarning < 1 && (! ctf->value) && timelimit->value > 3 && level.matchTime >= (timelimit->value - 3) * 60 )
+ {
+ CenterPrintAll( "3 MINUTES LEFT..." );
+ gi.sound( &g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, gi.soundindex("tng/3_minutes.wav"), 1.0, ATTN_NONE, 0.0 );
+ timewarning = 1;
+ }
+ }
+ }
+
+ return false;
+}
+
+int WonGame(int winner);
+
+static bool CheckRoundTimeLimit( void )
+{
+ if (roundtimelimit->value > 0)
+ {
+ int roundLimitFrames = (int)(roundtimelimit->value * 600);
+
+ if (current_round_length >= roundLimitFrames)
+ {
+ int winTeam = NOTEAM;
+
+ gi.LocBroadcast_Print( PRINT_HIGH, "Round timelimit hit.\n" );
+
+ winTeam = CheckForForcedWinner();
+ if (WonGame( winTeam ))
+ return true;
+
+ team_round_going = 0;
+ timewarning = fragwarning = 0;
+ lights_camera_action = 0;
+ holding_on_tie_check = 0;
+ team_round_countdown = 71;
+
+ return true;
+ }
+
+ if (use_warnings->value && timewarning < 2)
+ {
+ roundLimitFrames -= current_round_length;
+
+ if (roundLimitFrames <= 600)
+ {
+ CenterPrintAll( "1 MINUTE LEFT..." );
+ gi.sound( &g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, gi.soundindex( "tng/1_minute.wav" ), 1.0, ATTN_NONE, 0.0 );
+ timewarning = 2;
+ }
+ else if (roundLimitFrames <= 1800 && timewarning < 1 && roundtimelimit->value > 3)
+ {
+ CenterPrintAll( "3 MINUTES LEFT..." );
+ gi.sound( &g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, gi.soundindex( "tng/3_minutes.wav" ), 1.0, ATTN_NONE, 0.0 );
+ timewarning = 1;
+ }
+ }
+ }
+ return false;
+}
+
+static bool CheckRoundLimit( void )
+{
+ if (roundlimit->value > 0)
+ {
+ int i, winTeam = NOTEAM;
+
+ for (i = TEAM1; i <= teamCount; i++) {
+ if (teams[i].score >= (int)roundlimit->value) {
+ winTeam = i;
+ break;
+ }
+ }
+
+ if (winTeam != NOTEAM)
+ {
+ for (i = TEAM1; i < TEAM_TOP; i++) {
+ teams[i].ready = 0;
+ }
+
+ timewarning = fragwarning = 0;
+ if (matchmode->value) {
+ SendScores();
+ team_round_going = team_round_countdown = team_game_going = 0;
+ MakeAllLivePlayersObservers();
+ } else {
+ gi.LocBroadcast_Print( PRINT_HIGH, "Roundlimit hit.\n" );
+ EndDMLevel();
+ }
+ team_round_going = team_round_countdown = team_game_going = 0;
+ level.matchTime = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+// WonGame: returns true if we're exiting the level.
+int WonGame (int winner)
+{
+ edict_t *player, *cl_ent; // was: edict_t *player;
+ int i;
+ char arg[64];
+
+ gi.LocBroadcast_Print (PRINT_HIGH, "The round is over:\n");
+ if (winner == WINNER_TIE)
+ {
+ gi.LocBroadcast_Print (PRINT_HIGH, "It was a tie, no points awarded!\n");
+
+ if(use_warnings->value)
+ gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, level.snd_teamwins[0], 1.0, ATTN_NONE, 0.0);
+ PrintScores ();
+ }
+ else
+ {
+ gi.LocBroadcast_Print (PRINT_HIGH, "%s won!\n", TeamName(winner));
+ // AQ:TNG Igor[Rock] changing sound dir
+ if(use_warnings->value)
+ gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, level.snd_teamwins[winner], 1.0, ATTN_NONE, 0.0);
+ // end of changing sound dir
+ teams[winner].score++;
+ teams[winner].teamscore->integer = teams[winner].score;
+
+ // Is this for reporting scores in serverinfo?
+ //gi.cvar_forceset(teams[winner].teamscore->integer, teams[winner].score);
+
+ PrintScores ();
+
+ }
+
+ if (CheckTimelimit())
+ return 1;
+
+ if (CheckRoundLimit())
+ return 1;
+
+ // if (vCheckVote()) {
+ // EndDMLevel ();
+ // team_round_going = team_round_countdown = team_game_going = 0;
+ // return 1;
+ // }
+ //vNewRound ();
+
+ if (teamplay->value && (!timelimit->value || level.matchTime <= ((timelimit->value * 60) - 5)))
+ {
+ arg[0] = '\0';
+ for (i = 0; i < game.maxclients; i++)
+ {
+ cl_ent = &g_edicts[1 + i];
+
+ // if (cl_ent->inuse && cl_ent->client->resp.stat_mode == 2)
+ // Cmd_Stats_f(cl_ent, arg);
+ }
+ }
+ // Increment roundNum for tracking
+ game.roundNum++;
+
+ // Reset kill streaks in team modes
+ if (use_killcounts->value){
+ for (i = 0; i < game.maxclients; i++) {
+ cl_ent = g_edicts + 1 + i;
+ cl_ent->client->resp.streakKills = 0;
+ }
+ }
+
+ return 0;
+}
+
+
+int CheckTeamRules (void)
+{
+ int winner = WINNER_NONE, i;
+ int checked_tie = 0;
+ char buf[1024];
+ struct tm *now = NULL;
+ time_t tnow = 0;
+ char ltm[64] = "";
+ char mvdstring[512] = "";
+
+ if (lights_camera_action)
+ {
+ ContinueLCA ();
+ return 0;
+ }
+
+ if (team_round_going)
+ current_round_length++;
+
+ if (holding_on_tie_check)
+ {
+ holding_on_tie_check--;
+ if (holding_on_tie_check > 0)
+ return 0;
+ holding_on_tie_check = 0;
+ checked_tie = 1;
+ }
+
+ if (team_round_countdown)
+ {
+ team_round_countdown--;
+ if(!team_round_countdown)
+ {
+ if (BothTeamsHavePlayers ())
+ {
+ in_warmup = 0;
+ team_game_going = 1;
+ StartLCA();
+ }
+ else
+ {
+ if (!matchmode->value || TeamsReady())
+ CenterPrintAll ("Not enough players to play!");
+ else
+ CenterPrintAll ("Both Teams Must Be Ready!");
+
+ team_round_going = team_round_countdown = team_game_going = 0;
+ MakeAllLivePlayersObservers ();
+ }
+ }
+ else
+ {
+ if (team_round_countdown == 101)
+ {
+ gi.sound (&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD,
+ gi.soundindex ("world/10_0.wav"), 1.0, ATTN_NONE, 0.0);
+ if (printrules->value) {
+ PrintMatchRules();
+ }
+ }
+ }
+ if(team_round_countdown == 41 && !matchmode->value)
+ {
+ while(CheckForUnevenTeams(NULL));
+ }
+ }
+
+ // check these rules every 1.5 seconds...
+ if (++rulecheckfrequency % 15 && !checked_tie)
+ return 0;
+
+ // if (matchmode->value)
+ // {
+ // if (mm_allowlock->value)
+ // {
+ // for (i = TEAM1; i <= teamCount; i++)
+ // {
+ // if (teams[i].locked && !TeamHasPlayers( i ))
+ // {
+ // teams[i].locked = 0;
+ // sprintf( buf, "%s unlocked (no players)", TeamName( i ) );
+ // CenterPrintAll( buf );
+ // }
+ // }
+ // }
+ // }
+
+ if (!team_round_going)
+ {
+ RunWarmup();
+ if (CheckTimelimit())
+ return 1;
+
+ // if (vCheckVote()) {
+ // EndDMLevel ();
+ // team_round_going = team_round_countdown = team_game_going = 0;
+ // return 1;
+ // }
+
+ if (!team_round_countdown)
+ {
+ if (BothTeamsHavePlayers())
+ {
+ int warmup_length = max( warmup->value, round_begin->value );
+ char buf[64] = "";
+ sprintf( buf, "The round will begin in %d seconds!", warmup_length );
+ CenterPrintAll( buf );
+ team_round_countdown = warmup_length * 10 + 2;
+
+ // JBravo: Autostart q2pro MVD2 recording on the server
+ // mvd2 compat to be reviewed later
+ // if( use_mvd2->value )
+ // {
+ // tnow = time(NULL);
+ // now = localtime(&tnow);
+ // strftime( ltm, 64, "%Y%m%d-%H%M%S", now );
+ // snprintf( mvdstring, sizeof(mvdstring), "mvdrecord %s-%s\n", ltm, level.mapname );
+ // gi.AddCommandString( mvdstring );
+ // gi.LocBroadcast_Print( PRINT_HIGH, "Starting MVD recording to file %s-%s.mvd2\n", ltm, level.mapname );
+ // }
+ // JBravo: End MVD2
+ }
+ }
+ }
+ else
+ /* team_round_going */
+ {
+ if (!(gameSettings & GS_ROUNDBASED))
+ {
+ if (CheckTimelimit())
+ return 1;
+
+ if (ctf->value && CTFCheckRules())
+ {
+ ResetPlayers();
+ EndDMLevel();
+ team_round_going = team_round_countdown = team_game_going = 0;
+ return 1;
+ }
+
+ // if (vCheckVote()) {
+ // EndDMLevel ();
+ // team_round_going = team_round_countdown = team_game_going = 0;
+ // return 1;
+ // }
+
+ if (!BothTeamsHavePlayers())
+ {
+ if (!matchmode->value || TeamsReady())
+ CenterPrintAll( "Not enough players to play!" );
+ else
+ CenterPrintAll( "Both Teams Must Be Ready!" );
+
+ team_round_going = team_round_countdown = team_game_going = 0;
+ MakeAllLivePlayersObservers();
+
+ /* try to restart the game */
+ while (CheckForUnevenTeams( NULL ));
+ }
+ return 0; //CTF and teamDM dont need to check winner, its not round based
+ }
+
+ winner = CheckForWinner();
+ if (winner != WINNER_NONE)
+ {
+ if (!checked_tie)
+ {
+ holding_on_tie_check = 50;
+ return 0;
+ }
+ if (WonGame(winner))
+ return 1;
+
+ team_round_going = 0;
+ lights_camera_action = 0;
+ holding_on_tie_check = 0;
+ timewarning = fragwarning = 0;
+ team_round_countdown = 71;
+
+ return 0;
+ }
+
+ if (CheckRoundTimeLimit())
+ return 1;
+ }
+ return 0;
+}
+
+
+void A_Scoreboard (edict_t * ent)
+{
+ int wteam = 0;
+
+ if (ent->client->layout == LAYOUT_SCORES)
+ {
+ // blink header of the winning team during intermission
+ //if (level.intermission_framenum && ((level.realFramenum / FRAMEDIV) & 8))
+ if (level.intermissiontime && (level.time.milliseconds() % 2000) >= 125)
+ { // blink 1/8th second
+ if (teams[TEAM1].score > teams[TEAM2].score)
+ wteam = TEAM1;
+ else if (teams[TEAM2].score > teams[TEAM1].score)
+ wteam = TEAM2;
+ else if (teams[TEAM1].total > teams[TEAM2].total) // frag tie breaker
+ wteam = TEAM1;
+ else if (teams[TEAM2].total > teams[TEAM1].total)
+ wteam = TEAM2;
+
+ if(teamCount == 3)
+ {
+ if(wteam) {
+ if (teams[TEAM3].score > teams[wteam].score)
+ wteam = TEAM3;
+ else if (teams[TEAM3].score == teams[wteam].score) {
+ if(teams[TEAM3].total > teams[wteam].total)
+ wteam = TEAM3;
+ else if (teams[TEAM3].total == teams[wteam].total)
+ wteam = 0;
+ }
+ } else {
+ if(teams[TEAM3].score > teams[TEAM1].score)
+ wteam = TEAM3;
+ else if (teams[TEAM3].total > teams[TEAM1].total)
+ wteam = TEAM3;
+ }
+ }
+
+ if (wteam == 1)
+ ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
+ else if (wteam == 2)
+ ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
+ else if (wteam == 3 && (teamCount == 3))
+ ent->client->ps.stats[STAT_TEAM3_PIC] = 0;
+ else // tie game!
+ {
+ ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
+ ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
+ if(teamCount == 3)
+ ent->client->ps.stats[STAT_TEAM3_PIC] = 0;
+ }
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_TEAM1_PIC] = level.pic_teamskin[TEAM1];
+ ent->client->ps.stats[STAT_TEAM2_PIC] = level.pic_teamskin[TEAM2];
+ if (teamCount == 3)
+ ent->client->ps.stats[STAT_TEAM3_PIC] = level.pic_teamskin[TEAM3];
+ }
+
+ ent->client->ps.stats[STAT_TEAM1_SCORE] = teams[TEAM1].score;
+ ent->client->ps.stats[STAT_TEAM2_SCORE] = teams[TEAM2].score;
+ if (teamCount == 3)
+ ent->client->ps.stats[STAT_TEAM3_SCORE] = teams[TEAM3].score;
+ }
+}
+
+
+static int G_PlayerCmp( const void *p1, const void *p2 )
+{
+ gclient_t *a = *(gclient_t * const *)p1;
+ gclient_t *b = *(gclient_t * const *)p2;
+
+ if (a->resp.score != b->resp.score)
+ return b->resp.score - a->resp.score;
+
+ if (a->resp.deaths < b->resp.deaths)
+ return -1;
+ if (a->resp.deaths > b->resp.deaths)
+ return 1;
+
+ if (a->resp.damage_dealt > b->resp.damage_dealt)
+ return -1;
+ if (a->resp.damage_dealt < b->resp.damage_dealt)
+ return 1;
+
+ return 0;
+}
+
+int G_SortedClients( gclient_t **sortedList )
+{
+ int i, total = 0;
+ gclient_t *client;
+
+ for (i = 0, client = game.clients; i < game.maxclients; i++, client++) {
+ if (!client->pers.connected)
+ continue;
+
+ sortedList[total++] = client;
+ }
+
+ qsort( sortedList, total, sizeof( gclient_t * ), G_PlayerCmp );
+
+ return total;
+}
+
+int G_NotSortedClients( gclient_t **sortedList )
+{
+ int i, total = 0;
+ gclient_t *client;
+
+ for (i = 0, client = game.clients; i < game.maxclients; i++, client++) {
+ if (!client->pers.connected)
+ continue;
+
+ sortedList[total++] = client;
+ }
+
+ return total;
+}
+
+#define MAX_SCOREBOARD_SIZE 1024
+#define TEAM_HEADER_WIDTH 160 //skin icon and team tag
+#define TEAM_ROW_CHARS 32 //"yv 42 string2 \"name\" "
+#define TEAM_ROW_WIDTH 160 //20 chars, name and possible captain tag
+#define TEAM_ROW_CHARS2 44 //yv %d string%c \"%-15s %3d %3d %3d\" "
+#define TEAM_ROW_WIDTH2 216 //27 chars, name Frg Tim Png
+#define TEAM_ROW_GAP 30
+
+// Maximum number of lines of scores to put under each team's header.
+#define MAX_SCORES_PER_TEAM 9
+
+#define MAX_PLAYERS_PER_TEAM 8
+
+void A_NewScoreboardMessage(edict_t * ent)
+{
+ char buf[1024];
+ char string[1024] = { '\0' };
+ gclient_t *sortedClients[MAX_CLIENTS];
+ int total[TEAM_TOP] = { 0, 0, 0, 0 };
+ int i, j, line = 0, lineh = 8;
+ int dead, alive, totalClients, maxPlayers, printCount;
+ gclient_t *cl;
+ edict_t *cl_ent;
+
+ // show alive players when dead
+ dead = (!IS_ALIVE(ent) || !team_round_going);
+ if (limchasecam->value != 0)
+ dead = 0;
+
+ totalClients = G_SortedClients(sortedClients);
+
+ for(i = 0; i < totalClients; i++) {
+ total[sortedClients[i]->resp.team]++;
+ }
+
+ // print teams
+ for (i = TEAM1; i <= teamCount; i++)
+ {
+ snprintf( buf, sizeof( buf ), "xv 44 yv %d string2 \"%3d %-11.11s Frg Tim Png\"", line++ * lineh, teams[i].score, teams[i].name );
+ Q_strlcat( string, buf, sizeof( string ) );
+
+ snprintf( buf, sizeof( buf ), "xv 44 yv %d string2 \"%s\" ",
+ line++ * lineh,
+ "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F \x9D\x9E\x9F \x9D\x9E\x9F \x9D\x9E\x9F"
+ );
+ Q_strlcat( string, buf, sizeof( string ) );
+
+ if (!total[i])
+ continue;
+
+ printCount = 0;
+ if (total[i] > MAX_PLAYERS_PER_TEAM)
+ maxPlayers = MAX_PLAYERS_PER_TEAM - 1;
+ else
+ maxPlayers = total[i];
+
+ for (j = 0; j < totalClients; j++)
+ {
+ cl = sortedClients[j];
+ if (cl->resp.team != i)
+ continue;
+
+ cl_ent = g_edicts + 1 + (cl - game.clients);
+ alive = IS_ALIVE(cl_ent);
+
+
+ snprintf( buf, sizeof( buf ), "xv 44 yv %d string%c \"%-15s %3d %3d %3d\"",
+ line++ * lineh,
+ (alive && dead ? '2' : ' '),
+ cl->pers.netname,
+ cl->resp.score,
+ (level.time - cl->resp.entertime).minutes(),
+ min(cl->ping, 999) );
+ Q_strlcat( string, buf, sizeof( string ) );
+ printCount++;
+ if (printCount >= maxPlayers)
+ break;
+ }
+
+ // show the amount of excess players
+ if (total[i] > MAX_PLAYERS_PER_TEAM) {
+ snprintf( buf, sizeof( buf ), "xv 44 yv %d string \" ..and %d more\"", line++ * lineh, total[i] - MAX_PLAYERS_PER_TEAM + 1 );
+ Q_strlcat( string, buf, sizeof( string ) );
+ }
+
+ line++;
+ }
+
+ string[sizeof( string ) - 1] = '\0';
+ if (strlen( string ) > MAX_SCOREBOARD_SIZE - 1) {
+ string[MAX_SCOREBOARD_SIZE - 1] = '\0';
+ }
+
+ gi.WriteByte( svc_layout );
+ gi.WriteString( string );
+}
+
+//AQ2:TNG - Slicer Modified Scores for Match Mode
+void A_ScoreboardMessage (edict_t * ent, edict_t * killer)
+{
+ char string[2048];
+ gclient_t *sortedClients[MAX_CLIENTS], *cl;
+ edict_t *cl_ent;
+ int totalClients;
+ int maxsize = 1000, i, j, line_x, line_y;
+
+ string[0] = 0;
+
+ if (ent->client->layout == LAYOUT_SCORES)
+ {
+ char footer[256], playername[16];
+ int team, len, footerLen = 0, remaining, deadview;
+ int total[TEAM_TOP] = {0,0,0,0};
+ int totalsubs[TEAM_TOP] = {0,0,0,0};
+ int totalscore[TEAM_TOP] = {0,0,0,0};
+ int totalalive[TEAM_TOP] = {0,0,0,0};
+ int totalaliveprinted;
+ int name_pos, flagindex = 0;
+ int tpic[TEAM_TOP][2] = {{0,0},{24,26},{25,27},{30,31}};
+ char temp[16];
+ int maxPlayersPerTeam, scoreWidth = 3, rowWidth, rowChars, rowGap, headerOffset = 0;
+ int maxPlayers, printCount, base_x, showExtra = 0, subLines = 0;
+
+ // new scoreboard for regular teamplay up to 16 players
+ if (use_newscore->value == 1 && teamplay->value && !matchmode->value && !ctf->value) {
+ A_NewScoreboardMessage(ent);
+ return;
+ }
+
+ if (use_newscore->value > 1 && teamCount < 3) {
+ showExtra = 1;
+ rowWidth = max(TEAM_HEADER_WIDTH, TEAM_ROW_WIDTH2);
+ rowChars = TEAM_ROW_CHARS2;
+ rowGap = TEAM_ROW_GAP;
+ } else {
+ rowWidth = max(TEAM_HEADER_WIDTH, TEAM_ROW_WIDTH);
+ rowChars = TEAM_ROW_CHARS;
+ rowGap = 0;
+ }
+
+ base_x = 160 - ((rowWidth + rowGap) * teamCount) / 2 + rowGap / 2;
+
+ if(ctf->value)
+ {
+ base_x += 8;
+ tpic[TEAM1][0] = 30;
+ tpic[TEAM2][0] = 31;
+ if(teamCount == 3)
+ tpic[TEAM3][0] = 32;
+ }
+ else if(teamdm->value)
+ {
+ scoreWidth = 3;
+ }
+
+ deadview = (!IS_ALIVE(ent) || !team_round_going);
+ // AQ:TNG - Hack to keep scoreboard from revealing whos alive during matches - JBravo
+ if (limchasecam->value != 0)
+ deadview = 0;
+
+ if (noscore->value)
+ totalClients = G_NotSortedClients(sortedClients);
+ else
+ totalClients = G_SortedClients(sortedClients);
+
+ ent->client->ps.stats[STAT_TEAM_HEADER] = level.pic_teamtag;
+
+ for (i = 0; i < totalClients; i++) {
+ cl = sortedClients[i];
+ team = cl->resp.team;
+
+ if (cl->resp.subteam) {
+ totalsubs[team]++;
+ continue;
+ }
+
+ cl_ent = g_edicts + 1 + (cl - game.clients);
+ if (IS_ALIVE(cl_ent))
+ totalalive[team]++;
+
+ totalscore[team] += cl->resp.score;
+ total[team]++;
+ }
+
+ len = 0;
+ //Build team headers
+ if (use_newscore->value > 2 && rowWidth > TEAM_HEADER_WIDTH)
+ headerOffset = (rowWidth - TEAM_HEADER_WIDTH) / 2;
+
+ rowWidth += rowGap;
+
+ sprintf(string + len, "yv 8 ");
+ len = strlen(string);
+ //Add skin img
+ for (i = TEAM1, line_x = base_x + headerOffset; i <= teamCount; i++, line_x += rowWidth)
+ {
+ sprintf(string + len,
+ "if %i xv %i pic %i endif ",
+ tpic[i][0], line_x, tpic[i][0]);
+ len = strlen(string);
+ }
+
+ //Add team tag img
+ if (!ctf->value) {
+ Q_strlcat(string, "if 22 ", sizeof(string));
+ len = strlen(string);
+ for (i = TEAM1, line_x = base_x + headerOffset; i <= teamCount; i++, line_x += rowWidth)
+ {
+ sprintf(string + len, "xv %i pic 22 ", line_x + 32);
+ len = strlen(string);
+ }
+ Q_strlcat(string, "endif ", sizeof(string));
+ len = strlen(string);
+ }
+
+ //Add player info
+ sprintf( string + len, "yv 28 " );
+ len = strlen( string );
+ for (i = TEAM1, line_x = base_x + headerOffset; i <= teamCount; i++, line_x += rowWidth)
+ {
+ if (matchmode->value)
+ snprintf(temp, sizeof(temp), "%4i/%2i/%-2d", totalscore[i], total[i], totalsubs[i]);
+ else
+ snprintf(temp, sizeof(temp), "%4i/%2i", totalscore[i], total[i]);
+
+ sprintf( string + len,
+ "xv %i string \"%s\" ",
+ line_x + 32, temp );
+ len = strlen( string );
+ }
+
+ //Add score
+ sprintf( string + len, "yv 12 " );
+ len = strlen( string );
+ for (i = TEAM1, line_x = base_x + headerOffset; i <= teamCount; i++, line_x += rowWidth)
+ {
+ sprintf( string + len,
+ "xv %i num %i %i ",
+ line_x + (ctf->value ? 90 : 96), scoreWidth, tpic[i][1] );
+ len = strlen( string );
+ }
+
+ //Add team name
+ sprintf( string + len, "yv 0 " );
+ len = strlen( string );
+ for (i = TEAM1, line_x = base_x + headerOffset; i <= teamCount; i++, line_x += rowWidth)
+ {
+ name_pos = ((20 - strlen( teams[i].name )) / 2) * 8;
+ if (name_pos < 0)
+ name_pos = 0;
+
+ sprintf( string + len,
+ "xv %d string \"%s\" ",
+ line_x + name_pos, teams[i].name );
+ len = strlen( string );
+ }
+
+ //Build footer
+ footer[0] = 0;
+ if (matchmode->value)
+ {
+ int secs, mins;
+
+ i = level.matchTime;
+ mins = i / 60;
+ secs = i % 60;
+
+ sprintf( footer, "yv 128 " );
+ footerLen = strlen( footer );
+ for (i = TEAM1, line_x = base_x + 39; i <= teamCount; i++, line_x += rowWidth)
+ {
+ sprintf( footer + footerLen, "xv %i string2 \"%s\" ",
+ line_x, teams[i].ready ? "Ready" : "Not Ready" );
+ footerLen = strlen( footer );
+ }
+
+ sprintf( footer + footerLen, "xv 112 yv 144 string \"Time: %d:%02d\" ", mins, secs );
+ footerLen = strlen( footer );
+ }
+
+ remaining = MAX_SCOREBOARD_SIZE - 1 - len - footerLen;
+
+ maxPlayersPerTeam = MAX_SCORES_PER_TEAM;
+ if (maxPlayersPerTeam > (remaining / rowChars) / teamCount)
+ maxPlayersPerTeam = (remaining / rowChars) / teamCount;
+
+ for (i = TEAM1, line_x = base_x; i <= teamCount; i++, line_x += rowWidth)
+ {
+ line_y = 42;
+ sprintf( string + len, "xv %i ", line_x );
+ len = strlen(string);
+
+ if (showExtra) {
+ sprintf( string + len, "yv %d string2 \"Name Frg Tim Png\" ", line_y );
+ len = strlen( string );
+ line_y += 8;
+ }
+
+ // if (ctf->value)
+ // flagindex = (i == TEAM1) ? items[FLAG_T1_NUM].index : items[FLAG_T2_NUM].index;
+
+ printCount = 0;
+ totalaliveprinted = 0;
+
+ maxPlayers = maxPlayersPerTeam;
+
+ if (matchmode->value) {
+ subLines = 1 + min(totalsubs[i], 2); //for subs
+ if (maxPlayers - subLines < 4)
+ subLines = 1;
+
+ maxPlayers -= subLines;
+ }
+
+ if (total[i] > maxPlayers)
+ maxPlayers = maxPlayers - 1;
+ else
+ maxPlayers = total[i];
+
+ if (maxPlayers)
+ {
+ for (j = 0; j < totalClients; j++)
+ {
+ cl = sortedClients[j];
+
+ if (cl->resp.team != i)
+ continue;
+ if (cl->resp.subteam)
+ continue;
+
+ cl_ent = g_edicts + 1 + (cl - game.clients);
+ if (IS_ALIVE(cl_ent))
+ totalaliveprinted++;
+
+ playername[0] = 0;
+ // if (IS_CAPTAIN(cl_ent)) {
+ // playername[0] = '@';
+ // playername[1] = 0;
+ // }
+ Q_strlcat(playername, cl->pers.netname, sizeof(playername));
+ if (showExtra) {
+ sprintf( string + len,
+ "yv %d string%s \"%-15s %3d %3d %3d\" ",
+ line_y,
+ (deadview && cl_ent->solid != SOLID_NOT) ? "2" : "",
+ playername,
+ cl->resp.score,
+ (level.time - cl->resp.entertime).minutes());
+ min(cl->ping, 999) );
+ } else {
+ sprintf( string + len,
+ "yv %i string%s \"%s\" ",
+ line_y,
+ (deadview && cl_ent->solid != SOLID_NOT) ? "2" : "",
+ playername );
+ }
+
+ len = strlen( string );
+ if (ctf->value && cl->inventory[flagindex]){
+ sprintf( string + len, "xv %i picn sbfctf%s xv %i ", line_x - 8, i == TEAM1 ? "2" : "1", line_x );
+ len = strlen( string );
+ }
+
+ line_y += 8;
+ printCount++;
+ if (printCount >= maxPlayers)
+ break;
+ }
+
+ // Print remaining players if we ran out of room...
+ if (printCount < total[i])
+ {
+ if (!deadview) // live player viewing scoreboard...
+ {
+ sprintf( string + len,
+ "yv %i string \"..and %i more\" ",
+ line_y, total[i] - printCount );
+ len = strlen( string );
+ }
+ else // dead player viewing scoreboard...
+ {
+ sprintf( string + len,
+ "yv %i string%s \"..and %i/%i more\" ",
+ line_y,
+ (totalalive[i] - totalaliveprinted) ? "2" : "",
+ totalalive[i] - totalaliveprinted,
+ total[i] - printCount );
+ len = strlen( string );
+ }
+ line_y += 8;
+ }
+ }
+
+ if (subLines == 1 && totalsubs[i]) //Subs
+ {
+ line_y = 96;
+ sprintf( string + len, "yv %i string2 \"%d Subs\" ", line_y, totalsubs[i] );
+ len = strlen( string );
+ line_y += 8;
+
+ }
+ else if (subLines > 0)
+ {
+ line_y = 96;
+ sprintf( string + len, "yv %i string2 \"Subs\" ", line_y );
+ len = strlen( string );
+
+ line_y += 8;
+ printCount = 0;
+
+ if (totalsubs[i] > subLines - 1)
+ maxPlayers = subLines - 2;
+ else
+ maxPlayers = totalsubs[i];
+
+ if (maxPlayers)
+ {
+ for (j = 0; j < totalClients; j++)
+ {
+ cl = sortedClients[j];
+
+ if (cl->resp.team != i)
+ continue;
+ if (!cl->resp.subteam)
+ continue;
+
+ cl_ent = g_edicts + 1 + (cl - game.clients);
+ sprintf( string + len,
+ "yv %d string \"%s%s\" ",
+ line_y, "", cl->pers.netname );
+ len = strlen( string );
+
+ line_y += 8;
+ printCount++;
+ if (printCount >= maxPlayers)
+ break;
+ }
+
+ // Print remaining players if we ran out of room...
+ if (printCount < totalsubs[i])
+ {
+ sprintf( string + len,
+ "yv %i string \" + %i more\" ",
+ line_y, totalsubs[i] - printCount );
+ len = strlen( string );
+
+ line_y += 8;
+ }
+ }
+ }
+ }
+
+ if (footerLen) {
+ string[MAX_SCOREBOARD_SIZE - 1 - footerLen] = 0;
+ Q_strlcat(string, footer, sizeof(string));
+ }
+ }
+ else if (ent->client->layout == LAYOUT_SCORES2)
+ {
+ const char *sb = scoreboard->string;
+ int sb_len = 0;
+ int chars = 0;
+
+ if( ! sb[0] )
+ {
+ if( noscore->value )
+ sb = "NMP";
+ else if( ctf->value )
+ sb = "SNMPCT";
+ else if( teamdm->value )
+ sb = "FNMPDT";
+ else
+ sb = "FNMPIT";
+ }
+
+ sb_len = strlen(sb);
+
+ for( i = 0; i < sb_len; i ++ )
+ {
+ char field = toupper( sb[ i ] );
+ if( i )
+ chars ++;
+ if( field == 'F' ) chars += 5;
+ else if( field == 'T' ) chars += 4;
+ else if( field == 'N' ) chars += 15;
+ else if( field == 'M' ) chars += 4;
+ else if( field == 'P' ) chars += 4;
+ else if( field == 'C' ) chars += 4;
+ else if( field == 'S' ) chars += 5;
+ else if( field == 'K' ) chars += 5;
+ else if( field == 'D' ) chars += 6;
+ else if( field == 'I' ) chars += 6;
+ else if( field == 'A' ) chars += 3;
+ else chars ++;
+ }
+
+ if (noscore->value)
+ totalClients = G_NotSortedClients(sortedClients);
+ else
+ totalClients = G_SortedClients(sortedClients);
+
+ line_x = 160 - (chars * 4);
+ sprintf( string, "xv %i yv 32 string2 \"", line_x );
+
+ for( i = 0; i < sb_len; i ++ )
+ {
+ char field = toupper( sb[ i ] );
+ if( i )
+ strcat( string, " " );
+
+ if( field == 'F' ) strcat( string, "Frags" );
+ else if( field == 'T' ) strcat( string, "Team" );
+ else if( field == 'N' ) strcat( string, "Player " );
+ else if( field == 'M' ) strcat( string, "Time" );
+ else if( field == 'P' ) strcat( string, "Ping" );
+ else if( field == 'C' ) strcat( string, "Caps" );
+ else if( field == 'S' ) strcat( string, "Score" );
+ else if( field == 'K' ) strcat( string, "Kills" );
+ else if( field == 'D' ) strcat( string, "Deaths" );
+ else if( field == 'I' ) strcat( string, "Damage" );
+ else if( field == 'A' ) strcat( string, "Acc" );
+ else sprintf( string + strlen(string), "%c", sb[ i ] );
+ }
+
+ strcat( string, "\" yv 40 string2 \"" );
+
+ for( i = 0; i < sb_len; i ++ )
+ {
+ char field = toupper( sb[ i ] );
+ if( i )
+ strcat( string, " " );
+
+ if( field == 'F' ) strcat( string, "\x9D\x9E\x9E\x9E\x9F" );
+ else if( field == 'T' ) strcat( string, "\x9D\x9E\x9E\x9F" );
+ else if( field == 'N' ) strcat( string, "\x9D\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9E\x9F" );
+ else if( field == 'M' ) strcat( string, "\x9D\x9E\x9E\x9F" );
+ else if( field == 'P' ) strcat( string, "\x9D\x9E\x9E\x9F" );
+ else if( field == 'C' ) strcat( string, "\x9D\x9E\x9E\x9F" );
+ else if( field == 'S' ) strcat( string, "\x9D\x9E\x9E\x9E\x9F" );
+ else if( field == 'K' ) strcat( string, "\x9D\x9E\x9E\x9E\x9F" );
+ else if( field == 'D' ) strcat( string, "\x9D\x9E\x9E\x9E\x9E\x9F" );
+ else if( field == 'I' ) strcat( string, "\x9D\x9E\x9E\x9E\x9E\x9F" );
+ else if( field == 'A' ) strcat( string, "\x9D\x9E\x9F" );
+ else sprintf( string + strlen(string), "%c", sb[ i ] );
+ }
+
+ strcat( string, "\" " );
+
+ line_y = 48;
+
+ for (i = 0; i < totalClients; i++)
+ {
+ cl = sortedClients[i];
+ cl_ent = g_edicts + 1 + (cl - game.clients);
+
+ sprintf( string + strlen(string), "yv %i string \"", line_y );
+
+ for( j = 0; j < sb_len; j ++ )
+ {
+ char buf[ 16 ] = "";
+ char field = toupper( sb[ j ] );
+ if( j )
+ strcat( string, " " );
+
+ if( field == 'F' ) snprintf( buf, sizeof(buf), "%5i", min( 99999, cl->resp.score ) );
+ else if( field == 'T' )
+ {
+ // if( matchmode->value )
+ // {
+ // char suffix = ' ';
+ // if( IS_CAPTAIN(cl_ent) )
+ // suffix = 'C';
+ // else if( cl->resp.subteam )
+ // suffix = 'S';
+ // snprintf( buf, sizeof(buf), " %c%c", (cl->resp.team ? (cl->resp.team + '0') : ' '), suffix );
+ // }
+ // else
+ snprintf( buf, sizeof(buf), " %c", (cl->resp.team ? (cl->resp.team + '0') : ' ') );
+ }
+ else if( field == 'N' ) snprintf( buf, sizeof(buf), "%-15s", cl->pers.netname );
+ else if( field == 'M' )
+ {
+ int minutes = (level.time - cl->resp.entertime).minutes();
+ if( minutes < 60 )
+ snprintf( buf, sizeof(buf), "%4i", minutes );
+ else if( minutes < 600 )
+ snprintf( buf, sizeof(buf), "%1i:%02i", minutes / 60, minutes % 60 );
+ else
+ snprintf( buf, sizeof(buf), "%3ih", min( 999, minutes / 60 ) );
+ }
+ else if( field == 'P' )
+ {
+ snprintf( buf, sizeof(buf), "%4i", min( 9999, cl->ping ) );
+ }
+ else if( field == 'C' ) snprintf( buf, sizeof(buf), "%4i", min( 9999, cl->resp.ctf_caps ) );
+ else if( field == 'S' ) snprintf( buf, sizeof(buf), "%5i", min( 99999, cl->resp.score ) );
+ else if( field == 'K' ) snprintf( buf, sizeof(buf), "%5i", min( 99999, cl->resp.kills) );
+ else if( field == 'D' ) snprintf( buf, sizeof(buf), "%6i", min( 999999, cl->resp.deaths) );
+ else if( field == 'I' ) snprintf( buf, sizeof(buf), "%6i", min( 999999, cl->resp.damage_dealt) );
+ else if( field == 'A' ) snprintf( buf, sizeof(buf), "%3.f", cl->resp.shotsTotal ? (double) cl->resp.hitsTotal * 100.0 / (double) cl->resp.shotsTotal : 0. );
+ else sprintf( buf, "%c", sb[ j ] );
+
+ strcat( string, buf );
+ }
+
+ strcat( string, "\" " );
+
+ line_y += 8;
+ if (strlen(string) > (maxsize - 100) && i < (totalClients - 2))
+ {
+ sprintf (string + strlen (string), "yv %d string \"..and %d more\" ",
+ line_y, (totalClients - i - 1) );
+ line_y += 8;
+ break;
+ }
+ }
+ }
+
+ if (strlen(string) > MAX_SCOREBOARD_SIZE - 1) { // for debugging...
+ gi.Com_PrintFmt("Warning: scoreboard string neared or exceeded max length\nDump:\n%s\n---\n", string);
+ string[MAX_SCOREBOARD_SIZE - 1] = '\0';
+ }
+
+ gi.WriteByte(svc_layout);
+ gi.WriteString(string);
+}
+
+// called when we enter the intermission
+void TallyEndOfLevelTeamScores (void)
+{
+ int i;
+ gi.sound (&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD,
+ gi.soundindex ("world/xian1.wav"), 1.0, ATTN_NONE, 0.0);
+
+ teams[TEAM1].total = teams[TEAM2].total = teams[TEAM3].total = 0;
+ for (i = 0; i < game.maxclients; i++)
+ {
+ if (!g_edicts[i + 1].inuse)
+ continue;
+ if (game.clients[i].resp.team == NOTEAM)
+ continue;
+
+ teams[game.clients[i].resp.team].total += game.clients[i].resp.score;
+ }
+ game.roundNum = 0;
+}
+
+
+/*
+ * Teamplay spawning functions...
+ */
+
+edict_t *SelectTeamplaySpawnPoint (edict_t * ent)
+{
+ return teamplay_spawns[ent->client->resp.team - 1];
+}
+
+// SpawnPointDistance:
+// Returns the distance between two spawn points (or any entities, actually...)
+float SpawnPointDistance (edict_t * spot1, edict_t * spot2)
+{
+ return Distance(spot1->s.origin, spot2->s.origin);
+}
+
+spawn_distances_t *spawn_distances;
+// GetSpawnPoints:
+// Put the spawn points into our potential_spawns array so we can work with them easily.
+void GetSpawnPoints (void)
+{
+ edict_t *spot = nullptr;
+
+ num_potential_spawns = 0;
+
+ if ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team1")) != nullptr)
+ {
+ potential_spawns[num_potential_spawns] = spot;
+ num_potential_spawns++;
+ }
+ if ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_team2")) != nullptr)
+ {
+ potential_spawns[num_potential_spawns] = spot;
+ num_potential_spawns++;
+ }
+
+ spot = nullptr;
+ while ((spot = G_FindByString<&edict_t::classname>(spot, "info_player_deathmatch")) != nullptr)
+ {
+ potential_spawns[num_potential_spawns] = spot;
+ num_potential_spawns++;
+ if (num_potential_spawns >= MAX_SPAWNS)
+ {
+ gi.Com_Print ("Warning: MAX_SPAWNS exceeded\n");
+ break;
+ }
+ }
+
+ if(spawn_distances)
+ gi.TagFree (spawn_distances);
+
+ spawn_distances = (spawn_distances_t *)gi.TagMalloc (num_potential_spawns *
+ sizeof (spawn_distances_t), TAG_GAME);
+}
+
+// compare_spawn_distances is used by the qsort() call
+int compare_spawn_distances (const void *sd1, const void *sd2)
+{
+ if (((spawn_distances_t *)sd1)->distance < ((spawn_distances_t *)sd2)->distance)
+ return -1;
+ else if (((spawn_distances_t *)sd1)->distance > ((spawn_distances_t *)sd2)->distance)
+ return 1;
+ else
+ return 0;
+}
+
+void SelectRandomTeamplaySpawnPoint (int team, bool teams_assigned[])
+{
+ teamplay_spawns[team] = potential_spawns[newrand(num_potential_spawns)];
+ teams_assigned[team] = true;
+}
+
+void SelectFarTeamplaySpawnPoint (int team, bool teams_assigned[])
+{
+ int x, y, spawn_to_use, preferred_spawn_points, num_already_used,
+ total_good_spawn_points;
+ float closest_spawn_distance, distance;
+
+ if (team < 0 || team >= MAX_TEAMS) {
+ gi.Com_Print( "Out-of-range teams value in SelectFarTeamplaySpawnPoint, skipping...\n" );
+ return;
+ }
+
+ num_already_used = 0;
+ for (x = 0; x < num_potential_spawns; x++)
+ {
+ closest_spawn_distance = 2000000000;
+
+ for (y = 0; y < teamCount; y++)
+ {
+ if (teams_assigned[y])
+ {
+ distance = SpawnPointDistance (potential_spawns[x], teamplay_spawns[y]);
+ if (distance < closest_spawn_distance)
+ closest_spawn_distance = distance;
+ }
+ }
+
+ if (closest_spawn_distance == 0)
+ num_already_used++;
+
+ spawn_distances[x].s = potential_spawns[x];
+ spawn_distances[x].distance = closest_spawn_distance;
+ }
+
+ qsort (spawn_distances, num_potential_spawns,
+ sizeof (spawn_distances_t), compare_spawn_distances);
+
+ total_good_spawn_points = num_potential_spawns - num_already_used;
+
+ if (total_good_spawn_points <= 4)
+ preferred_spawn_points = 1;
+ else if (total_good_spawn_points <= 10)
+ preferred_spawn_points = 2;
+ else
+ preferred_spawn_points = 3;
+
+ //FB 6/1/99 - make DF_SPAWN_FARTHEST force far spawn points in TP
+ if (g_dm_spawn_farthest->integer)
+ preferred_spawn_points = 1;
+ //FB 6/1/99
+
+ spawn_to_use = newrand (preferred_spawn_points);
+
+ teams_assigned[team] = true;
+ teamplay_spawns[team] = spawn_distances[num_potential_spawns - spawn_to_use - 1].s;
+}
+
+// SetupTeamSpawnPoints:
+//
+// Setup the points at which the teams will spawn.
+//
+void SetupTeamSpawnPoints ()
+{
+ bool teams_assigned[MAX_TEAMS];
+ int i, l;
+
+ for (l = 0; l < MAX_TEAMS; l++)
+ {
+ teamplay_spawns[l] = NULL;
+ teams_assigned[l] = false;
+ }
+
+ l = newrand(teamCount);
+
+ SelectRandomTeamplaySpawnPoint(l, teams_assigned);
+
+ for(i = l+1; i < teamCount+l; i++) {
+ SelectFarTeamplaySpawnPoint(i % teamCount, teams_assigned);
+ }
+}
+
+// TNG:Freud New Spawning system
+// NS_GetSpawnPoints:
+// Put the potential spawns into arrays for each team.
+void NS_GetSpawnPoints ()
+{
+ int x, i;
+
+ NS_randteam = newrand(teamCount);
+
+ for (x = 0; x < teamCount; x++)
+ {
+ NS_num_used_farteamplay_spawns[x] = 0;
+ NS_num_potential_spawns[x] = num_potential_spawns;
+ for(i = 0; i < num_potential_spawns; i++)
+ NS_potential_spawns[x][i] = potential_spawns[i];
+ }
+}
+
+// TNG:Freud
+// NS_SelectRandomTeamplaySpawnPoint
+// Select a random spawn point for the team from remaining spawns.
+// returns false if no spawns left.
+bool NS_SelectRandomTeamplaySpawnPoint (int team, bool teams_assigned[])
+{
+ int spawn_point, z;
+
+ if (NS_num_potential_spawns[team] < 1) {
+ gi.Com_Print("New Spawncode: gone through all spawns, re-reading spawns\n");
+ NS_GetSpawnPoints ();
+ NS_SetupTeamSpawnPoints ();
+ return false;
+ }
+
+ spawn_point = newrand (NS_num_potential_spawns[team]);
+ NS_num_potential_spawns[team]--;
+
+ teamplay_spawns[team] = NS_potential_spawns[team][spawn_point];
+ teams_assigned[team] = true;
+
+ for (z = spawn_point;z < NS_num_potential_spawns[team];z++) {
+ NS_potential_spawns[team][z] = NS_potential_spawns[team][z + 1];
+ }
+
+ return true;
+}
+
+// TNG:Freud
+#define MAX_USABLESPAWNS 3
+// NS_SelectFarTeamplaySpawnPoint
+// Selects farthest teamplay spawn point from available spawns.
+bool NS_SelectFarTeamplaySpawnPoint (int team, bool teams_assigned[])
+{
+ int u, x, y, z, spawn_to_use, preferred_spawn_points, num_already_used,
+ total_good_spawn_points;
+ float closest_spawn_distance, distance;
+ edict_t *usable_spawns[MAX_USABLESPAWNS];
+ bool used;
+ int num_usable;
+
+ if (team < 0 || team >= MAX_TEAMS) {
+ gi.Com_Print( "Out-of-range teams value in SelectFarTeamplaySpawnPoint, skipping...\n" );
+ return false;
+ }
+
+ num_already_used = 0;
+ for (x = 0; x < NS_num_potential_spawns[team]; x++)
+ {
+ closest_spawn_distance = 2000000000;
+
+ for (y = 0; y < teamCount; y++)
+ {
+ if (teams_assigned[y])
+ {
+ distance =
+ SpawnPointDistance (NS_potential_spawns[team][x], teamplay_spawns[y]);
+
+ if (distance < closest_spawn_distance)
+ closest_spawn_distance = distance;
+ }
+ }
+
+ if (closest_spawn_distance == 0)
+ num_already_used++;
+
+ spawn_distances[x].s = NS_potential_spawns[team][x];
+ spawn_distances[x].distance = closest_spawn_distance;
+ }
+
+ qsort (spawn_distances, NS_num_potential_spawns[team],
+ sizeof (spawn_distances_t), compare_spawn_distances);
+
+ total_good_spawn_points = NS_num_potential_spawns[team] - num_already_used;
+
+ if (total_good_spawn_points <= 4)
+ preferred_spawn_points = 1;
+ else if (total_good_spawn_points <= 10)
+ preferred_spawn_points = 2;
+ else
+ preferred_spawn_points = 3;
+
+ //FB 6/1/99 - make DF_SPAWN_FARTHEST force far spawn points in TP
+ if (g_dm_spawn_farthest->integer)
+ preferred_spawn_points = 1;
+ //FB 6/1/99
+
+ num_usable = 0;
+ for (z = 0;z < preferred_spawn_points;z++) {
+ used = false;
+ for (u = 0;u < NS_num_used_farteamplay_spawns[team];u++) {
+ if (NS_used_farteamplay_spawns[team][u] ==
+ spawn_distances[NS_num_potential_spawns[team] - z - 1].s) {
+ used = true;
+ break;
+ }
+ }
+ if (used == false) {
+ usable_spawns[num_usable] = spawn_distances[NS_num_potential_spawns[team] - z - 1].s;
+ num_usable++;
+ if (num_usable >= MAX_USABLESPAWNS) {
+ break;
+ }
+ }
+ }
+ if (num_usable < 1) {
+ NS_SetupTeamSpawnPoints();
+ return false;
+ }
+
+ spawn_to_use = newrand(num_usable);
+
+ NS_used_farteamplay_spawns[team][NS_num_used_farteamplay_spawns[team]] = usable_spawns[spawn_to_use];
+ NS_num_used_farteamplay_spawns[team]++;
+
+ teams_assigned[team] = true;
+ teamplay_spawns[team] = usable_spawns[spawn_to_use];
+
+ return true;
+}
+
+// TNG:Freud
+// NS_SetupTeamSpawnPoints
+// Finds and assigns spawn points to each team.
+void NS_SetupTeamSpawnPoints ()
+{
+ bool teams_assigned[MAX_TEAMS];
+ int l;
+
+ for (l = 0; l < MAX_TEAMS; l++) {
+ teamplay_spawns[l] = NULL;
+ teams_assigned[l] = false;
+ }
+
+ if (NS_SelectRandomTeamplaySpawnPoint (NS_randteam, teams_assigned) == false)
+ return;
+
+ for (l = 0; l < teamCount; l++) {
+ if (l != NS_randteam && NS_SelectFarTeamplaySpawnPoint(l, teams_assigned) == false)
+ return;
+ }
+}
+
+/*
+Simple function that just returns the opposite team number
+Obviously, do not use this for 3team functions
+*/
+int OtherTeam(int teamNum)
+{
+ if (teamNum < 0 || teamNum > TEAM2) {
+ gi.Com_Print("OtherTeam() was called but parameter supplied is not 1 or 2");
+ return 0;
+ }
+
+ if (teamNum == 1)
+ return TEAM2;
+ else
+ return TEAM1;
+
+ // Returns zero if teamNum is not 1 or 2
+ return 0;
+}
\ No newline at end of file
diff --git a/actionlite/action/a_team.h b/actionlite/action/a_team.h
new file mode 100644
index 0000000..e33adef
--- /dev/null
+++ b/actionlite/action/a_team.h
@@ -0,0 +1,110 @@
+#define NOTEAM 0
+#define TEAM1 1
+#define TEAM2 2
+#define TEAM3 3
+
+#define MAX_TEAMS 3
+#define TEAM_TOP (MAX_TEAMS+1)
+
+#define WINNER_NONE NOTEAM
+#define WINNER_TIE TEAM_TOP
+
+// Pre- and post-trace code for our teamplay anti-stick stuff. If there are
+// still "transparent" (SOLID_TRIGGER) players, they need to be set to
+// SOLID_BBOX before a trace is performed, then changed back again
+// afterwards. PRETRACE() and POSTTRACE() should be called before and after
+// traces in all places where combat is taking place (ie "transparent" players
+// should be detected), ie shots being traced etc.
+// FB 6/1/99: Now crouching players will have their bounding box adjusted here
+// too, for better shot areas. (there has to be a better way to do this?)
+
+#define PRETRACE() \
+ if (transparent_list && (((int)teamplay->value && !lights_camera_action))) \
+ TransparentListSet(SOLID_BBOX)
+
+#define POSTTRACE() \
+ if (transparent_list && (((int)teamplay->value && !lights_camera_action))) \
+ TransparentListSet(SOLID_TRIGGER)
+
+edict_t *SelectTeamplaySpawnPoint (edict_t *);
+bool FallingDamageAmnesty (edict_t * targ);
+char * TeamName (int team);
+void UpdateJoinMenu( void );
+void OpenJoinMenu (edict_t *);
+void OpenWeaponMenu (edict_t *);
+void OpenItemMenu (edict_t * ent);
+void OpenItemKitMenu (edict_t * ent);
+void JoinTeam (edict_t * ent, int desired_team, int skip_menuclose);
+edict_t *FindOverlap (edict_t * ent, edict_t * last_overlap);
+int CheckTeamRules (void);
+void A_Scoreboard (edict_t * ent);
+void Team_f (edict_t * ent);
+void AssignSkin (edict_t * ent, const char *s, bool nickChanged);
+void TallyEndOfLevelTeamScores (void);
+void SetupTeamSpawnPoints ();
+int CheckTeamSpawnPoints ();
+void GetSpawnPoints ();
+void CleanBodies (); // from p_client.c, removes all current dead bodies from map
+
+void LeaveTeam (edict_t *);
+int newrand (int top);
+void InitTransparentList ();
+void AddToTransparentList (edict_t *);
+void RemoveFromTransparentList (edict_t *);
+bool OnTransparentList( const edict_t *ent );
+void PrintTransparentList ();
+void CenterPrintAll (const char *msg);
+int TeamHasPlayers( int team );
+
+//TNG:Freud - new spawning system
+void NS_GetSpawnPoints ();
+bool NS_SelectFarTeamplaySpawnPoint (int team, bool teams_assigned[]);
+void NS_SetupTeamSpawnPoints ();
+
+int OtherTeam(int teamNum);
+
+typedef struct spawn_distances_s
+{
+ float distance;
+ edict_t *s;
+}
+spawn_distances_t;
+
+typedef struct transparent_list_s
+{
+ edict_t *ent;
+ struct transparent_list_s *next;
+}
+transparent_list_t;
+
+
+extern bool team_game_going;
+extern bool team_round_going;
+extern int lights_camera_action;
+extern int holding_on_tie_check;
+extern int team_round_countdown;
+extern int timewarning;
+extern int fragwarning;
+extern transparent_list_t *transparent_list;
+extern trace_t trace_t_temp;
+extern int current_round_length; // For RoundTimeLeft
+extern int day_cycle_at;
+extern int teamCount;
+extern int in_warmup;
+extern bool teams_changed;
+
+typedef struct menu_list_weapon
+{
+ int num;
+ char sound[40];
+ char name[40];
+}
+menu_list_weapon;
+
+typedef struct menu_list_item
+{
+ int num;
+ char sound[40];
+ char name[40];
+}
+menu_list_item;
diff --git a/actionlite/action/cgf_sfx_glass.cpp b/actionlite/action/cgf_sfx_glass.cpp
new file mode 100644
index 0000000..133650f
--- /dev/null
+++ b/actionlite/action/cgf_sfx_glass.cpp
@@ -0,0 +1,741 @@
+/****************************************************************************/
+/* */
+/* project : CGF (c) 1999 William van der Sterren */
+/* parts (c) 1998 id software */
+/* */
+/* file : cgf_sfx_glass.cpp "special effects for glass entities" */
+/* author(s): William van der Sterren */
+/* version : 0.5 */
+/* */
+/* date (last revision): Jun 12, 99 */
+/* date (creation) : Jun 04, 99 */
+/* */
+/* */
+/* revision history */
+/* -- date ---- | -- revision ---------------------- | -- revisor -- */
+/* Jun 12, 1999 | fixed knife slash breaks glass | William */
+/* Jun 08, 1999 | improved fragment limit | William */
+/* */
+/******* http://www.botepidemic.com/aid/cgf for CGF for Action Quake2 *******/
+//
+// $Id: cgf_sfx_glass.c,v 1.2 2001/09/28 13:48:34 ra Exp $
+//
+//-----------------------------------------------------------------------------
+// $Log: cgf_sfx_glass.c,v $
+// Revision 1.2 2001/09/28 13:48:34 ra
+// I ran indent over the sources. All .c and .h files reindented.
+//
+// Revision 1.1.1.1 2001/05/06 17:29:34 igor_rock
+// This is the PG Bund Edition V1.25 with all stuff laying around here...
+//
+//-----------------------------------------------------------------------------
+
+#ifdef __cplusplus
+ // VC++, for CGF
+#include // prevent problems between C and STL
+extern "C"
+{
+#include "../g_local.h"
+#include "cgf_sfx_glass.h"
+}
+#else
+ // C, for other AQ2 variants
+#include "g_local.h"
+#include "cgf_sfx_glass.h"
+#endif
+
+
+// cvar for breaking glass
+static cvar_t *breakableglass = 0;
+
+// cvar for max glass fragment count
+static cvar_t *glassfragmentlimit = 0;
+
+static int glassfragmentcount = 0;
+
+
+// additional functions - Q2 expects C calling convention
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void CGF_SFX_TouchGlass (edict_t * self, edict_t * other, cplane_t * plane,
+ csurface_t * surf);
+// called whenever an entity hits the trigger spawned for the glass
+
+ void CGF_SFX_EmitGlass (edict_t * aGlassPane, edict_t * anInflictor,
+ vec3_t aPoint);
+// emits glass fragments from aPoint, to show effects of firing thru window
+
+ void CGF_SFX_BreakGlass (edict_t * aGlassPane, edict_t * anOther,
+ edict_t * anAttacker, int aDamage, vec3_t aPoint,
+ vec_t aPaneDestructDelay);
+// breaks glass
+
+ void CGF_SFX_InstallBreakableGlass (edict_t * aGlassPane);
+// when working on a glass pane for the first time, just install trigger
+// when working on a glass pane again (after a game ended), move
+// glass back to original location
+
+ void CGF_SFX_HideBreakableGlass (edict_t * aGlassPane);
+// after being broken, the pane cannot be removed as it is needed in
+// subsequent missions/games, so hide it at about z = -1000
+
+ void CGF_SFX_ApplyGlassFragmentLimit (const char *aClassName);
+// updates glassfragmentcount and removes oldest glass fragement if
+// necessary to meet limit
+
+ void CGF_SFX_MiscGlassUse (edict_t * self, edict_t * other,
+ edict_t * activator);
+// catches use from unforeseen objects (weapons, debris,
+// etc. touching the window)
+
+ void CGF_SFX_MiscGlassDie (edict_t * self, edict_t * inflictor,
+ edict_t * attacker, int damage, vec3_t point);
+// catches die calls caused by unforeseen objects (weapons, debris,
+// etc. damaging the window)
+
+ void CGF_SFX_GlassThrowDebris (edict_t * self, char *modelname, float speed,
+ vec3_t origin);
+// variant of id software's ThrowDebris, now numbering the entity (for later removal)
+
+ extern // from a_game.c
+ edict_t *FindEdictByClassnum (char *classname, int classnum);
+
+// declaration from g_misc.c
+ extern // from g_misc.c
+ void debris_die (edict_t * self, edict_t * inflictor, edict_t * attacker,
+ int damage, vec3_t point);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+void
+CGF_SFX_InstallGlassSupport ()
+{
+ breakableglass = gi.cvar ("breakableglass", "0", 0);
+ glassfragmentlimit = gi.cvar ("glassfragmentlimit", "30", 0);
+}
+
+
+int
+CGF_SFX_IsBreakableGlassEnabled ()
+{
+ // returns whether breakable glass is enabled (cvar) and allowed (dm mode)
+ return breakableglass->value;
+}
+
+
+
+void
+CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think (edict_t *
+ aPossibleGlassEntity)
+{
+ // at level.time == 0.1 the entity has been introduced in the game,
+ // and we can use gi.pointcontents and gi.trace to check the entity
+ vec3_t origin;
+ int breakingglass;
+ trace_t trace;
+
+ // test for cvar
+ if (!CGF_SFX_IsBreakableGlassEnabled ())
+ {
+ G_FreeEdict (aPossibleGlassEntity);
+ return;
+ }
+
+ VectorAdd (aPossibleGlassEntity->absmax, aPossibleGlassEntity->absmin,
+ origin);
+ VectorScale (origin, 0.5, origin);
+
+ // detect glass (does not work for complex shapes,
+ // for example, the glass window near the satellite
+ // dish at Q2 base3
+ breakingglass = (gi.pointcontents (origin) & CONTENTS_TRANSLUCENT);
+
+ if (!breakingglass)
+ {
+ // test for complex brushes that happen to be
+ // hollow in their origin (for instance, the
+ // window at Q2 base3, near the satellite dish
+ trace =
+ gi.trace (origin, vec3_origin, vec3_origin,
+ aPossibleGlassEntity->absmax, 0, MASK_PLAYERSOLID);
+ breakingglass = ((trace.ent == aPossibleGlassEntity)
+ && (trace.contents & CONTENTS_TRANSLUCENT));
+ trace =
+ gi.trace (origin, vec3_origin, vec3_origin,
+ aPossibleGlassEntity->absmin, 0, MASK_PLAYERSOLID);
+ breakingglass = ((breakingglass)
+ || ((trace.ent == aPossibleGlassEntity)
+ && (trace.contents & CONTENTS_TRANSLUCENT)));
+ }
+
+ if (!breakingglass)
+ {
+ // do remove other func_explosives
+ G_FreeEdict (aPossibleGlassEntity);
+ return;
+ }
+
+ // discovered some glass - now make store the origin
+ // we need that after hiding the glass
+ VectorCopy (aPossibleGlassEntity->s.origin, aPossibleGlassEntity->pos1); // IMPORTANT!
+
+ // make a backup of the health in light_level
+ aPossibleGlassEntity->light_level = aPossibleGlassEntity->health;
+
+ // install the glass
+ CGF_SFX_InstallBreakableGlass (aPossibleGlassEntity);
+}
+
+
+
+void
+CGF_SFX_InstallBreakableGlass (edict_t * aGlassPane)
+{
+ // when working on a glass pane for the first time, just install trigger
+ // when working on a glass pane again (after a game ended), move
+ // glass back to original location
+ edict_t *trigger;
+ vec3_t maxs;
+ vec3_t mins;
+
+ // reset origin based on aGlassPane->pos1
+ VectorCopy (aGlassPane->pos1, aGlassPane->s.origin);
+
+ // reset health based on aGlassPane->light_level
+ aGlassPane->health = aGlassPane->light_level;
+
+ // replace die and use functions by glass specific ones
+ aGlassPane->die = CGF_SFX_MiscGlassDie;
+ aGlassPane->use = CGF_SFX_MiscGlassUse;
+
+ // reset some pane attributes
+ aGlassPane->takedamage = DAMAGE_YES;
+ aGlassPane->solid = SOLID_BSP;
+ aGlassPane->movetype = MOVETYPE_FLYMISSILE;
+ // for other movetypes, cannot move pane to hidden location and back
+
+ // try to establish size
+ VectorCopy (aGlassPane->maxs, maxs);
+ VectorCopy (aGlassPane->mins, mins);
+
+ // set up trigger, similar to triggers for doors
+ // but with a smaller box
+ mins[0] -= 24;
+ mins[1] -= 24;
+ mins[2] -= 24;
+ maxs[0] += 24;
+ maxs[1] += 24;
+ maxs[2] += 24;
+
+ // adjust some settings
+ trigger = G_Spawn ();
+ trigger->classname = "breakableglass_trigger";
+ VectorCopy (mins, trigger->mins);
+ VectorCopy (maxs, trigger->maxs);
+ trigger->owner = aGlassPane;
+ trigger->solid = SOLID_TRIGGER;
+ trigger->movetype = MOVETYPE_NONE;
+ trigger->touch = CGF_SFX_TouchGlass;
+ gi.linkentity (trigger);
+}
+
+
+void
+CGF_SFX_ShootBreakableGlass (edict_t * aGlassPane, edict_t * anAttacker,
+ /*trace_t* */ void *tr,
+ int mod)
+{
+ // process gunshots thru glass
+ edict_t *trigger;
+ int destruct;
+
+ // depending on mod, destroy window or emit fragments
+ switch (mod)
+ {
+ // break for ap, shotgun, handcannon, and kick, destory window
+ case MOD_M3:
+ case MOD_HC:
+ case MOD_SNIPER:
+ case MOD_KICK:
+ case MOD_GRENADE:
+ case MOD_G_SPLASH:
+ case MOD_HANDGRENADE:
+ case MOD_HG_SPLASH:
+ case MOD_KNIFE: // slash damage
+ destruct = true;
+ break;
+ default:
+ destruct = (rand () % 3 == 0);
+ break;
+ };
+
+ if (destruct)
+ {
+ // break glass (and hurt if doing kick)
+ CGF_SFX_BreakGlass (aGlassPane, anAttacker, 0, aGlassPane->health,
+ vec3_origin, FRAMETIME);
+ if (mod.id == MOD_KICK)
+ {
+ vec3_t bloodorigin;
+ vec3_t dir;
+ vec3_t normal;
+ VectorAdd (aGlassPane->absmax, aGlassPane->absmin, bloodorigin);
+ VectorScale (bloodorigin, 0.5, bloodorigin);
+ VectorSubtract (bloodorigin, anAttacker->s.origin, dir);
+ VectorNormalize (dir);
+ VectorMA (anAttacker->s.origin, 32.0, dir, bloodorigin);
+ VectorSet (normal, 0, 0, -1);
+ T_Damage (anAttacker, aGlassPane, anAttacker, dir, bloodorigin,
+ normal, 15.0, 0, 0, MOD_BREAKINGGLASS);
+ }
+
+ // remove corresponding trigger
+ trigger = 0;
+ while ((trigger = G_Find (trigger, FOFS (classname), "breakableglass_trigger")) != NULL)
+ {
+ if (trigger->owner == aGlassPane)
+ {
+ // remove it
+ G_FreeEdict (trigger);
+ // only one to be found
+ break;
+ }
+ }
+ }
+ else
+ {
+ // add decal (if not grenade)
+ if ((mod != MOD_HANDGRENADE)
+ && (mod != MOD_HG_SPLASH)
+ && (mod != MOD_GRENADE) && (mod != MOD_G_SPLASH))
+ {
+ AddDecal (anAttacker, (trace_t *) tr);
+ }
+ // and emit glass
+ CGF_SFX_EmitGlass (aGlassPane, anAttacker, ((trace_t *) tr)->endpos);
+ }
+}
+
+
+void
+CGF_SFX_TouchGlass (edict_t * self, edict_t * other, cplane_t * plane,
+ csurface_t * surf)
+{
+ // called whenever an entity hits the trigger spawned for the glass
+
+ vec3_t origin;
+ vec3_t normal;
+ vec3_t spot;
+ trace_t trace;
+ edict_t *glass;
+ vec3_t velocity;
+ vec_t speed;
+ vec_t projected_speed;
+ int is_hgrenade;
+ int is_knife;
+
+ is_hgrenade = is_knife = false;
+
+ // ignore non-clients-non-grenade-non-knife
+ if (!other->client)
+ {
+ is_knife = (0 == Q_stricmp ("weapon_knife", other->classname));
+ if (!is_knife)
+ {
+ is_hgrenade = (0 == Q_stricmp ("hgrenade", other->classname));
+ }
+
+ if ((!is_knife) && (!is_hgrenade))
+ return;
+ if (is_knife)
+ goto knife_and_grenade_handling;
+ }
+
+ // test whether other really hits the glass - deal with
+ // the special case that other hits some boundary close to the border of the glass pane
+ //
+ //
+ // ....trigger.......
+ // +++++++++ +++++++++++
+ // .+---glass------+.
+ // wall .+--------------+.wall
+ // +++++++++ +++++++++++
+ // ----->..................
+ // wrong ^ ^
+ // | |
+ // wrong ok
+ //
+ glass = self->owner;
+ // hack - set glass' movetype to MOVETYPE_PUSH as it is not
+ // moving as long as the trigger is active
+ glass->movetype = MOVETYPE_PUSH;
+
+ VectorAdd (glass->absmax, glass->absmin, origin);
+ VectorScale (origin, 0.5, origin);
+
+ // other needs to be able to trace to glass origin
+ trace = gi.trace (other->s.origin, vec3_origin, vec3_origin, origin, other,
+ MASK_PLAYERSOLID);
+ if (trace.ent != glass)
+ return;
+
+ // we can reach the glass origin, so we have the normal of
+ // the glass plane
+ VectorCopy (trace.plane.normal, normal);
+
+ // we need to check if client is not running into wall next
+ // to the glass (the trigger stretches into the wall)
+ VectorScale (normal, -1000.0, spot);
+ VectorAdd (spot, other->s.origin, spot);
+ // line between other->s.origin and spot (perpendicular to glass
+ // surface should not hit wall but glass instead
+ trace = gi.trace (other->s.origin, vec3_origin, vec3_origin, spot, other,
+ MASK_PLAYERSOLID);
+ if (trace.ent != glass)
+ return;
+
+ // now, we check if the client's speed perpendicular to
+ // the glass plane, exceeds the required 175
+ // (speed should be < -200, as the plane's normal
+ // points towards the client
+ VectorCopy (other->velocity, velocity);
+ speed = VectorNormalize (velocity);
+ projected_speed = speed * DotProduct (velocity, normal);
+
+ // bump projected speed for grenades - they should break
+ // the window more easily
+ if (is_hgrenade)
+ projected_speed *= 1.5f;
+
+ // if hitting the glass with sufficient speed (project < -175),
+ // being jumpkicked (speed > 700, project < -5) break the window
+ if (!((projected_speed < -175.0) ||
+ ((projected_speed < -5) && (speed > 700))))
+ goto knife_and_grenade_handling;
+
+ // break glass
+ CGF_SFX_BreakGlass (glass, other, other, glass->health, vec3_origin,
+ 3.0f * FRAMETIME);
+ // glass can take care of itself, but the trigger isn't needed anymore
+ G_FreeEdict (self);
+
+ /* not needed
+ // reduce momentum of the client (he just broke the window
+ // so he should lose speed. in addition, it doesn't feel
+ // right if he overtakes the glass fragments
+ // VectorScale(normal, 200.0, velocity);
+ // VectorAdd(other->velocity, velocity, other->velocity);
+ */
+
+ // make sure client takes damage
+ T_Damage (other, glass, other, normal, other->s.origin, normal, 15.0, 0, 0,
+ MOD_BREAKINGGLASS);
+ return;
+
+ // goto label
+knife_and_grenade_handling:
+ // if knife or grenade, bounce them
+ if ((is_knife) || (is_hgrenade))
+ {
+ // change clipmask to bounce of glass
+ other->clipmask = MASK_SOLID;
+ }
+}
+
+
+void
+CGF_SFX_BreakGlass (edict_t * aGlassPane, edict_t * anInflictor,
+ edict_t * anAttacker, int aDamage, vec3_t aPoint,
+ vec_t aPaneDestructDelay)
+{
+ // based on func_explode, but with lotsa subtle differences
+ vec3_t origin;
+ vec3_t old_origin;
+ vec3_t chunkorigin;
+ vec3_t size;
+ int count;
+ int mass;
+
+ // bmodel origins are (0 0 0), we need to adjust that here
+ VectorCopy (aGlassPane->s.origin, old_origin);
+ VectorScale (aGlassPane->size, 0.5, size);
+ VectorAdd (aGlassPane->absmin, size, origin);
+ VectorCopy (origin, aGlassPane->s.origin);
+
+ aGlassPane->takedamage = DAMAGE_NO;
+
+ VectorSubtract (aGlassPane->s.origin, anInflictor->s.origin,
+ aGlassPane->velocity);
+ VectorNormalize (aGlassPane->velocity);
+ // use speed 250 instead of 150 for funkier glass spray
+ VectorScale (aGlassPane->velocity, 250.0, aGlassPane->velocity);
+
+ // start chunks towards the center
+ VectorScale (size, 0.75, size);
+
+ mass = aGlassPane->mass;
+ if (!mass)
+ mass = 75;
+
+ // big chunks
+ if (mass >= 100)
+ {
+ count = mass / 100;
+ if (count > 8)
+ count = 8;
+ while (count--)
+ {
+ CGF_SFX_ApplyGlassFragmentLimit ("debris");
+ chunkorigin[0] = origin[0] + crandom () * size[0];
+ chunkorigin[1] = origin[1] + crandom () * size[1];
+ chunkorigin[2] = origin[2] + crandom () * size[2];
+ CGF_SFX_GlassThrowDebris (aGlassPane,
+ "models/objects/debris1/tris.md2", 1,
+ chunkorigin);
+ }
+ }
+
+ // small chunks
+ count = mass / 25;
+ if (count > 16)
+ count = 16;
+ while (count--)
+ {
+ CGF_SFX_ApplyGlassFragmentLimit ("debris");
+ chunkorigin[0] = origin[0] + crandom () * size[0];
+ chunkorigin[1] = origin[1] + crandom () * size[1];
+ chunkorigin[2] = origin[2] + crandom () * size[2];
+ CGF_SFX_GlassThrowDebris (aGlassPane, "models/objects/debris2/tris.md2",
+ 2, chunkorigin);
+ }
+
+ // clear velocity, reset origin (that has been abused in ThrowDebris)
+ VectorClear (aGlassPane->velocity);
+ VectorCopy (old_origin, aGlassPane->s.origin);
+
+ if (anAttacker)
+ {
+ // jumping thru
+ G_UseTargets (aGlassPane, anAttacker);
+ }
+ else
+ {
+ // firing thru - the pane has no direct attacker to hurt,
+ // but G_UseTargets expects one. So make it a DIY
+ G_UseTargets (aGlassPane, aGlassPane);
+ }
+
+ // have glass plane be visible for two more frames,
+ // and have it self-destruct then
+ // meanwhile, make sure the player can move thru
+ aGlassPane->solid = SOLID_NOT;
+ aGlassPane->think = CGF_SFX_HideBreakableGlass;
+ aGlassPane->nextthink = level.framenum + aPaneDestructDelay * HZ;
+}
+
+
+
+void
+CGF_SFX_EmitGlass (edict_t * aGlassPane, edict_t * anInflictor, vec3_t aPoint)
+{
+ // based on func_explode, but with lotsa subtle differences
+ vec3_t old_origin;
+ vec3_t chunkorigin;
+ vec3_t size;
+ int count;
+
+ // bmodel origins are (0 0 0), we need to adjust that here
+ VectorCopy (aGlassPane->s.origin, old_origin);
+ VectorCopy (aPoint, aGlassPane->s.origin);
+
+ VectorSubtract (aGlassPane->s.origin, anInflictor->s.origin,
+ aGlassPane->velocity);
+ VectorNormalize (aGlassPane->velocity);
+ // use speed 250 instead of 150 for funkier glass spray
+ VectorScale (aGlassPane->velocity, 250.0, aGlassPane->velocity);
+
+ // start chunks towards the center
+ VectorScale (aGlassPane->size, 0.25, size);
+
+ count = 4;
+ while (count--)
+ {
+ CGF_SFX_ApplyGlassFragmentLimit ("debris");
+ chunkorigin[0] = aPoint[0] + crandom () * size[0];
+ chunkorigin[1] = aPoint[1] + crandom () * size[1];
+ chunkorigin[2] = aPoint[2] + crandom () * size[2];
+ CGF_SFX_GlassThrowDebris (aGlassPane, "models/objects/debris2/tris.md2",
+ 2, chunkorigin);
+ }
+
+ // clear velocity, reset origin (that has been abused in ThrowDebris)
+ VectorClear (aGlassPane->velocity);
+ VectorCopy (old_origin, aGlassPane->s.origin);
+
+ // firing thru - the pane has no direct attacker to hurt,
+ // but G_UseTargets expects one. So make it a DIY
+ G_UseTargets (aGlassPane, aGlassPane);
+}
+
+
+
+void
+CGF_SFX_HideBreakableGlass (edict_t * aGlassPane)
+{
+ // remove all attached decals
+ edict_t *decal;
+ decal = 0;
+ while ((decal = G_Find (decal, FOFS (classname), "decal")) != NULL)
+ {
+ if (decal->owner == aGlassPane)
+ {
+ // make it goaway in the next frame
+ decal->think = G_FreeEdict;
+ decal->nextthink = level.framenum + 1;
+ }
+ }
+
+ while ((decal = G_Find (decal, FOFS (classname), "splat")) != NULL)
+ {
+ if (decal->owner == aGlassPane)
+ {
+ // make it goaway in the next frame
+ decal->think = G_FreeEdict;
+ decal->nextthink = level.framenum + 1;
+ }
+ }
+
+ // after being broken, the pane cannot be freed as it is needed in
+ // subsequent missions/games, so hide it at about z = -1000 lower
+ aGlassPane->movetype = MOVETYPE_FLYMISSILE;
+ VectorCopy (aGlassPane->s.origin, aGlassPane->pos1);
+ aGlassPane->s.origin[2] -= 1000.0;
+}
+
+
+
+void
+CGF_SFX_AttachDecalToGlass (edict_t * aGlassPane, edict_t * aDecal)
+{
+ // just set aDecal's owner to be the glass pane
+ aDecal->owner = aGlassPane;
+}
+
+
+
+void
+CGF_SFX_RebuildAllBrokenGlass ()
+{
+ // iterate over all func_explosives
+ edict_t *glass;
+ glass = 0;
+ while ((glass = G_Find (glass, FOFS (classname), "func_explosive")) != NULL)
+ {
+ // glass is broken if solid != SOLID_BSP
+ if (glass->solid != SOLID_BSP)
+ {
+ CGF_SFX_InstallBreakableGlass (glass);
+ }
+ }
+}
+
+
+
+void
+CGF_SFX_ApplyGlassFragmentLimit (const char *aClassName)
+{
+ edict_t *oldfragment;
+
+ glassfragmentcount++;
+ if (glassfragmentcount > glassfragmentlimit->value)
+ glassfragmentcount = 1;
+
+ // remove fragment with corresponding number if any
+ oldfragment = FindEdictByClassnum ((char *) aClassName, glassfragmentcount);
+
+ if (oldfragment)
+ {
+ // oldfragment->nextthink = level.framenum + 1;
+ G_FreeEdict (oldfragment);
+ }
+}
+
+
+
+void
+CGF_SFX_MiscGlassUse (edict_t * self, edict_t * other, edict_t * activator)
+{
+#ifdef _DEBUG
+ const char *classname;
+ classname = other->classname;
+#endif
+}
+
+
+
+void
+CGF_SFX_MiscGlassDie (edict_t * self, edict_t * inflictor, edict_t * attacker,
+ int damage, vec3_t point)
+{
+#ifdef _DEBUG
+ const char *classname;
+ classname = inflictor->classname;
+#endif
+}
+
+
+
+static vec_t previous_throw_time = 0;
+static int this_throw_count = 0;
+
+void
+CGF_SFX_GlassThrowDebris (edict_t * self, char *modelname, float speed,
+ vec3_t origin)
+{
+ // based on ThrowDebris from id software - now returns debris created
+ edict_t *chunk;
+ vec3_t v;
+
+ if (level.time != previous_throw_time)
+ {
+ previous_throw_time = level.time;
+ this_throw_count = 0;
+ }
+ else
+ {
+ this_throw_count++;
+ if (this_throw_count > glassfragmentlimit->value)
+ return;
+ }
+
+ chunk = G_Spawn ();
+ VectorCopy (origin, chunk->s.origin);
+ gi.setmodel (chunk, modelname);
+ v[0] = 100 * crandom ();
+ v[1] = 100 * crandom ();
+ v[2] = 100 + 100 * crandom ();
+ VectorMA (self->velocity, speed, v, chunk->velocity);
+ chunk->movetype = MOVETYPE_BOUNCE;
+ chunk->solid = SOLID_NOT;
+ chunk->avelocity[0] = random () * 600;
+ chunk->avelocity[1] = random () * 600;
+ chunk->avelocity[2] = random () * 600;
+ chunk->think = G_FreeEdict;
+ chunk->nextthink = level.framenum + (5 + random() * 5) * HZ;
+ chunk->s.frame = 0;
+ chunk->flags = 0;
+ chunk->classname = "debris";
+ chunk->takedamage = DAMAGE_YES;
+ chunk->die = debris_die;
+ gi.linkentity (chunk);
+
+ // number chunk
+ chunk->classnum = glassfragmentcount;
+}
diff --git a/actionlite/action/cgf_sfx_glass.h b/actionlite/action/cgf_sfx_glass.h
new file mode 100644
index 0000000..0a70b94
--- /dev/null
+++ b/actionlite/action/cgf_sfx_glass.h
@@ -0,0 +1,83 @@
+/****************************************************************************/
+/* */
+/* project : CGF (c) 1999 William van der Sterren */
+/* parts (c) 1998 id software */
+/* */
+/* file : cgf_sfx_glass.h "special effects for glass entities" */
+/* author(s): William van der Sterren */
+/* version : 0.5 */
+/* */
+/* date (last revision): Jun 12, 99 */
+/* date (creation) : Jun 04, 99 */
+/* */
+/* */
+/* revision history */
+/* -- date ---- | -- revision ---------------------- | -- revisor -- */
+/* Jun 12, 1999 | fixed knife slash breaks glass | William */
+/* */
+/******* http://www.botepidemic.com/aid/cgf for CGF for Action Quake2 *******/
+//
+// $Id: cgf_sfx_glass.h,v 1.2 2001/09/28 13:48:34 ra Exp $
+//
+//-----------------------------------------------------------------------------
+// $Log: cgf_sfx_glass.h,v $
+// Revision 1.2 2001/09/28 13:48:34 ra
+// I ran indent over the sources. All .c and .h files reindented.
+//
+// Revision 1.1.1.1 2001/05/06 17:29:34 igor_rock
+// This is the PG Bund Edition V1.25 with all stuff laying around here...
+//
+//-----------------------------------------------------------------------------
+
+#ifndef __CGF_SFX_GLASS_H_
+#define __CGF_SFX_GLASS_H_
+
+
+// defines (should be consistent with other weapon defs in g_local.h)
+#define MOD_BREAKINGGLASS 46
+
+/*
+ // forward definitions
+ typedef struct edict_s edict_t;
+ */
+
+
+// export a number of functions to g_func.c:
+//
+//
+
+void CGF_SFX_InstallGlassSupport ();
+// registers cvar breakableglass (default 0)
+// registers cvar glassfragmentlimit (default 30)
+
+
+void CGF_SFX_RebuildAllBrokenGlass ();
+// upon starting a new team play game, reconstruct any
+// broken glass because we like to break it again
+
+
+int CGF_SFX_IsBreakableGlassEnabled ();
+// returns whether breakable glass is enabled (cvar breakableglass)
+
+
+void CGF_SFX_TestBreakableGlassAndRemoveIfNot_Think (edict_t *
+ aPossibleGlassEntity);
+// initial think function for all func_explosives
+// because we cannot verify the contents of the entity unless the entity
+// has been spawned, we need to first introduce the entity, and remove
+// it later (level.time == 0.1) if required
+
+
+void CGF_SFX_ShootBreakableGlass (edict_t * aGlassPane, edict_t * anAttacker,
+ /*trace_t */ void *tr,
+ int mod);
+// shoot thru glass - depending on bullet types and
+// random effects, glass just emits fragments, or breaks
+
+
+void CGF_SFX_AttachDecalToGlass (edict_t * aGlassPane, edict_t * aDecal);
+// a glass that breaks will remove all decals (blood splashes, bullet
+// holes) if they are attached
+
+
+#endif
diff --git a/actionlite/g_local.h b/actionlite/g_local.h
index 860b180..bc65b4b 100644
--- a/actionlite/g_local.h
+++ b/actionlite/g_local.h
@@ -10,12 +10,7 @@
#include "action/a_team.h"
#include "action/a_game.h"
#include "action/cgf_sfx_glass.h"
-#include "action/a_match.h"
#include "action/a_radio.h"
-#include "action/a_ini.h"
-#include "action/a_stats.h"
-#include "action/a_balancer.h"
-
// the "gameversion" client command will print this plus compile date
constexpr const char *GAMEVERSION = "action";
@@ -1678,15 +1673,23 @@ extern cvar_t *medkit_max;
extern cvar_t *medkit_value;
extern cvar_t *stats_endmap; // If on (1), show the accuracy/etc stats at the end of a map
extern cvar_t *stats_afterround; // TNG Stats, collect stats between rounds
-
+extern cvar_t *printrules;
extern cvar_t *auto_join; // Automaticly join clients to teams they were on in last map.
extern cvar_t *auto_equip; // Remember weapons and items for players between maps.
extern cvar_t *auto_menu; // Automatically show the join menu
-
+extern cvar_t *use_newscore; // Use the new scoreboard
+extern cvar_t *noscore; // Don't show the scoreboard
+extern cvar_t *scoreboard; // Scoreboard style
+extern cvar_t *ir;
+extern cvar_t *knifelimit;
+extern cvar_t *tgren;
extern cvar_t *dm_choose;
extern cvar_t *dm_shield;
extern cvar_t *uvtime;
extern cvar_t *warmup;
+extern cvar_t *rrot;
+extern cvar_t *vrot;
+
extern mod_id_t meansOfDeath;
@@ -1694,12 +1697,16 @@ extern mod_id_t meansOfDeath;
extern int locOfDeath;
// stop an armor piercing round that hits a vest
extern int stopAP;
+extern cvar_t *eventeams;
+extern cvar_t *use_balancer;
+bool CheckForUnevenTeams (edict_t *);
+bool IsAllowedToJoin(edict_t *, int);
void TransparentListSet (solid_t solid_type);
-char *GetPossesiveAdjectiveSingular(edict_t *ent);
-char *GetPossesiveAdjective(edict_t *ent);
-char *GetReflexivePronoun(edict_t *ent);
+const char *GetPossesiveAdjectiveSingular(edict_t *ent);
+const char *GetPossesiveAdjective(edict_t *ent);
+const char *GetReflexivePronoun(edict_t *ent);
// Action Add end
@@ -1933,9 +1940,13 @@ extern gitem_t itemlist[IT_TOTAL];
//======================================================================
// Action Add
//======================================================================
-
+#define PARSE_BUFSIZE 256
#define IS_ALIVE(ent) ((ent)->solid != SOLID_NOT && (ent)->deadflag)
+#define WEAPON_COUNT 9
+#define ITEM_COUNT 6
+#define AMMO_COUNT 5
+
#define GS_DEATHMATCH 1
#define GS_TEAMPLAY 2
#define GS_MATCHMODE 4
@@ -1996,6 +2007,7 @@ extern cvar_t *weapon_respawn;
extern cvar_t *ammo_respawn;
extern cvar_t *hc_single;
extern cvar_t *use_punch;
+extern cvar_t *radiolog;
extern cvar_t *radio_max;
extern cvar_t *radio_time;
extern cvar_t *radio_ban;
@@ -2764,6 +2776,10 @@ void fire_doppleganger(edict_t *ent, const vec3_t &start, const vec3_t &aimdir)
void RemoveAttackingPainDaemons(edict_t *self);
bool G_ShouldPlayersCollide(bool weaponry);
bool P_UseCoopInstancedItems();
+void Add_Frag(edict_t * ent, int mod);
+void Subtract_Frag(edict_t * ent);
+void Add_Death( edict_t *ent, bool end_streak );
+void PrintDeathMessage(char *msg, edict_t * gibee);
// ACTION
void CL_FixUpGender(edict_t *ent, const char *userinfo);
void ClientFixLegs(edict_t *ent);
@@ -3000,6 +3016,7 @@ struct client_respawn_t
int32_t totalidletime;
edict_t *kickvote;
+ int32_t *menu_shown;
char *mapvote; // pointer to map voted on (if any)
char *cvote; // pointer to config voted on (if any)
bool scramblevote; // want scramble
@@ -3281,6 +3298,7 @@ struct gclient_t
edict_t *chase_target;
int32_t chase_mode;
+ bool team_force; // are we forcing a team change
// ammo capacities
int32_t ammo_index;
int32_t max_pistolmags;
diff --git a/actionlite/g_main.cpp b/actionlite/g_main.cpp
index 7da5a0b..328edce 100644
--- a/actionlite/g_main.cpp
+++ b/actionlite/g_main.cpp
@@ -172,6 +172,13 @@ cvar_t *use_killcounts;
cvar_t *use_rewards;
cvar_t *warmup;
cvar_t *round_begin;
+cvar_t *eventeams;
+cvar_t *use_balancer;
+cvar_t *ir;
+cvar_t *knifelimit;
+cvar_t *tgren;
+cvar_t *rrot;
+cvar_t *vrot;
// UI / Menu / Messaging Settings
cvar_t *motd_time;
@@ -181,7 +188,10 @@ cvar_t *stats_afterround; // TNG Stats, collect stats between rounds
cvar_t *auto_join; // Automaticly join clients to teams they were on in last map.
cvar_t *auto_equip; // Remember weapons and items for players between maps.
cvar_t *auto_menu; // Automatically show the join menu
-
+cvar_t *printrules; // Print the rules at the start of the game
+cvar_t *use_newscore; // Use the new scoreboard
+cvar_t *noscore; // Don't show the scoreboard
+cvar_t *scoreboard;
// Weapon and Item Settings
cvar_t *allitem;
diff --git a/actionlite/q_std.h b/actionlite/q_std.h
index 73e6dda..0d131b3 100644
--- a/actionlite/q_std.h
+++ b/actionlite/q_std.h
@@ -270,4 +270,32 @@ int BoxOnPlaneSide (const vec3_t emins, const vec3_t emaxs, const struct cplane_
float anglemod (float a);
//float LerpAngle (float a1, float a2, float frac);
+#define Q_isupper( c ) ( (c) >= 'A' && (c) <= 'Z' )
+#define Q_islower( c ) ( (c) >= 'a' && (c) <= 'z' )
+#define Q_isdigit( c ) ( (c) >= '0' && (c) <= '9' )
+#define Q_isalpha( c ) ( Q_isupper( c ) || Q_islower( c ) )
+#define Q_isalnum( c ) ( Q_isalpha( c ) || Q_isdigit( c ) )
+
+int Q_tolower( int c );
+int Q_toupper( int c );
+
+char *Q_strlwr( char *s );
+char *Q_strupr( char *s );
+
+int Q_tolower( int c ) {
+ if (Q_isupper( c )) {
+ c += ('a' - 'A');
+ }
+
+ return c;
+}
+
+int Q_toupper( int c ) {
+ if (Q_islower( c )) {
+ c -= ('a' - 'A');
+ }
+
+ return c;
+}
+
// EOF