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