1553 lines
52 KiB
C
1553 lines
52 KiB
C
/*
|
|
* Teamplay-related code for Action (formerly Axshun).
|
|
* Some of this is borrowed from Zoid's CTF (thanks Zoid)
|
|
* -Fireblade
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
#include "cgf_sfx_glass.h"
|
|
|
|
qboolean team_game_going = 0; // is a team game going right now?
|
|
qboolean team_round_going = 0; // 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 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 team1_score = 0;
|
|
int team2_score = 0;
|
|
int team1_total = 0;
|
|
int team2_total = 0;
|
|
|
|
#define MAX_SPAWNS 1000 // 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 num_teams = 2; // teams in current game, fixed at 2 for now...
|
|
|
|
transparent_list_t *transparent_list = NULL;
|
|
|
|
|
|
void CreditsMenu(edict_t *ent, pmenu_t *p);
|
|
|
|
|
|
void InitTransparentList()
|
|
{
|
|
if (transparent_list != NULL)
|
|
{
|
|
transparent_list_t *p, *q;
|
|
|
|
p = transparent_list;
|
|
while (p != NULL)
|
|
{
|
|
q = p->next;
|
|
gi.TagFree(p);
|
|
p = q;
|
|
}
|
|
|
|
transparent_list = NULL;
|
|
}
|
|
}
|
|
|
|
void AddToTransparentList(edict_t *ent)
|
|
{
|
|
transparent_list_t *p, *n;
|
|
|
|
n = (transparent_list_t *)gi.TagMalloc(sizeof(transparent_list_t), TAG_GAME);
|
|
if (n == NULL)
|
|
{
|
|
gi.dprintf("Out of memory\n");
|
|
exit(1);
|
|
}
|
|
n->ent = ent;
|
|
n->next = NULL;
|
|
|
|
if (transparent_list == NULL)
|
|
{
|
|
transparent_list = n;
|
|
}
|
|
else
|
|
{
|
|
p = transparent_list;
|
|
while (p->next != NULL)
|
|
{
|
|
p = p->next;
|
|
}
|
|
p->next = n;
|
|
}
|
|
}
|
|
|
|
void RemoveFromTransparentList(edict_t *ent)
|
|
{
|
|
transparent_list_t *p, *q, *r;
|
|
|
|
if (transparent_list != NULL)
|
|
{
|
|
if (transparent_list->ent == ent)
|
|
{
|
|
q = transparent_list->next;
|
|
gi.TagFree(transparent_list);
|
|
transparent_list = q;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
p = transparent_list;
|
|
q = p->next;
|
|
while (q != NULL)
|
|
{
|
|
if (q->ent == ent)
|
|
{
|
|
r = q->next;
|
|
gi.TagFree(q);
|
|
p->next = r;
|
|
return;
|
|
}
|
|
p = p->next;
|
|
q = p->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi.dprintf("Warning: attempt to RemoveFromTransparentList when not in it\n");
|
|
}
|
|
|
|
void TransparentListSet(solid_t solid_type)
|
|
{
|
|
transparent_list_t *p = transparent_list;
|
|
|
|
while (p != NULL)
|
|
{
|
|
p->ent->solid = solid_type;
|
|
gi.linkentity(p->ent);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
void ReprintMOTD(edict_t *ent, pmenu_t *p)
|
|
{
|
|
PMenu_Close(ent);
|
|
PrintMOTD(ent);
|
|
}
|
|
|
|
void JoinTeam1(edict_t *ent, pmenu_t *p)
|
|
{
|
|
JoinTeam(ent, TEAM1, 0);
|
|
}
|
|
|
|
void JoinTeam2(edict_t *ent, pmenu_t *p)
|
|
{
|
|
JoinTeam(ent, TEAM2, 0);
|
|
}
|
|
|
|
void SelectWeapon2(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(MP5_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon3(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(M3_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon4(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(HC_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon5(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(SNIPER_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon6(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(M4_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon0(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(KNIFE_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectWeapon9(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.weapon = FindItem(DUAL_NAME);
|
|
PMenu_Close(ent);
|
|
OpenItemMenu(ent);
|
|
}
|
|
|
|
void SelectItem1(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.item = FindItem(KEV_NAME);
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
void SelectItem2(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.item = FindItem(LASER_NAME);
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
void SelectItem3(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.item = FindItem(SLIP_NAME);
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
void SelectItem4(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.item = FindItem(SIL_NAME);
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
void SelectItem5(edict_t *ent, pmenu_t *p)
|
|
{
|
|
ent->client->resp.item = FindItem(BAND_NAME);
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
void CreditsReturnToMain(edict_t *ent, pmenu_t *p)
|
|
{
|
|
PMenu_Close(ent);
|
|
OpenJoinMenu(ent);
|
|
}
|
|
|
|
pmenu_t creditsmenu[] = {
|
|
{ "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "--------------", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Head Design, Original Programming", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Sam 'Cail' Thompson", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Sounds, Second-In-Command", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Patrick 'Bartender' Mills", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Original Programming", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Nathan 'Pietro' Kovner", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Michael 'Siris' Taylor", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Skins, Etc", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Jon 'Vain' Delee", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Levels", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Evan 'Ace12GA' Prentice", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Models, Skins, Etc", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Minh 'Gooseman' Le", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Dallas 'Suislide' Frank", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "*Action 1.5 ('Axshun') Programming", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Carl 'Zucchini' Schedvin", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Bob 'Fireblade' Farmer", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
|
|
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "Return to main menu", PMENU_ALIGN_LEFT, NULL, CreditsReturnToMain },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL },
|
|
};
|
|
|
|
pmenu_t weapmenu[] = {
|
|
{ "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "--------------", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Select your weapon", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "MP5/10 Submachinegun", PMENU_ALIGN_LEFT, NULL, SelectWeapon2 },
|
|
{ "M3 Super90 Assault Shotgun", PMENU_ALIGN_LEFT, NULL, SelectWeapon3 },
|
|
{ "Handcannon", PMENU_ALIGN_LEFT, NULL, SelectWeapon4 },
|
|
{ "SSG 3000 Sniper Rifle", PMENU_ALIGN_LEFT, NULL, SelectWeapon5 },
|
|
{ "M4 Assault Rifle", PMENU_ALIGN_LEFT, NULL, SelectWeapon6 },
|
|
{ "Combat Knives", PMENU_ALIGN_LEFT, NULL, SelectWeapon0 },
|
|
{ "Akimbo Pistols", PMENU_ALIGN_LEFT, NULL, SelectWeapon9 },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL },
|
|
};
|
|
|
|
pmenu_t itemmenu[] = {
|
|
{ "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "--------------", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ "Select your item", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "Kevlar Vest", PMENU_ALIGN_LEFT, NULL, SelectItem1 },
|
|
{ "Laser Sight", PMENU_ALIGN_LEFT, NULL, SelectItem2 },
|
|
{ "Stealth Slippers", PMENU_ALIGN_LEFT, NULL, SelectItem3 },
|
|
{ "Silencer", PMENU_ALIGN_LEFT, NULL, SelectItem4 },
|
|
{ "Bandolier", PMENU_ALIGN_LEFT, NULL, SelectItem5 },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL },
|
|
};
|
|
|
|
pmenu_t joinmenu[] = {
|
|
{ "*Action Quake 2", PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ NULL /* lvl name */, PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_CENTER, NULL, NULL },
|
|
{ NULL /* team 1 */, PMENU_ALIGN_LEFT, NULL, JoinTeam1 },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL /* team 2 */, PMENU_ALIGN_LEFT, NULL, JoinTeam2 },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "MOTD", PMENU_ALIGN_LEFT, NULL, ReprintMOTD },
|
|
{ "Credits", PMENU_ALIGN_LEFT, NULL, CreditsMenu },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "TAB to exit menu", PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ NULL, PMENU_ALIGN_LEFT, NULL, NULL },
|
|
{ "v" ACTION_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL },
|
|
};
|
|
|
|
void CreditsMenu(edict_t *ent, pmenu_t *p)
|
|
{
|
|
PMenu_Close(ent);
|
|
PMenu_Open(ent, creditsmenu, 4, sizeof(creditsmenu) / sizeof(pmenu_t));
|
|
}
|
|
|
|
char *TeamName(int team)
|
|
{
|
|
if (team == TEAM1)
|
|
return team1_name;
|
|
else if (team == TEAM2)
|
|
return team2_name;
|
|
else
|
|
return "None";
|
|
}
|
|
|
|
void AssignSkin(edict_t *ent, char *s)
|
|
{
|
|
int playernum = ent-g_edicts-1;
|
|
|
|
switch (ent->client->resp.team)
|
|
{
|
|
case TEAM1:
|
|
gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s",
|
|
ent->client->pers.netname, team1_skin) );
|
|
break;
|
|
case TEAM2:
|
|
gi.configstring (CS_PLAYERSKINS+playernum,
|
|
va("%s\\%s", ent->client->pers.netname, team2_skin) );
|
|
break;
|
|
default:
|
|
gi.configstring (CS_PLAYERSKINS+playernum,
|
|
va("%s\\%s", ent->client->pers.netname, s) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Team_f(edict_t *ent)
|
|
{
|
|
char *t;
|
|
int desired_team;
|
|
|
|
t = gi.args();
|
|
if (!*t)
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "You are on %s.\n",
|
|
TeamName(ent->client->resp.team));
|
|
return;
|
|
}
|
|
|
|
if (level.framenum < (ent->client->resp.joined_team + 50))
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "You must wait 5 seconds before changing teams again.\n");
|
|
return;
|
|
}
|
|
|
|
if (Q_stricmp(t, "none") == 0)
|
|
{
|
|
if (ent->client->resp.team == NOTEAM)
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "You're not on a team.\n");
|
|
}
|
|
else
|
|
{
|
|
LeaveTeam(ent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (Q_stricmp(t, "1") == 0)
|
|
desired_team = TEAM1;
|
|
else if (Q_stricmp(t, "2") == 0)
|
|
desired_team = TEAM2;
|
|
else if (Q_stricmp(t, team1_name) == 0)
|
|
desired_team = TEAM1;
|
|
else if (Q_stricmp(t, team2_name) == 0)
|
|
desired_team = TEAM2;
|
|
else
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
|
|
return;
|
|
}
|
|
|
|
if (ent->client->resp.team == desired_team)
|
|
{
|
|
gi.cprintf(ent, PRINT_HIGH, "You are already on %s.\n",
|
|
TeamName(ent->client->resp.team));
|
|
return;
|
|
}
|
|
|
|
JoinTeam(ent, desired_team, 1);
|
|
}
|
|
|
|
void UnevenTeamsMsg(int whichteam, int uneven_amount, char *opponent)
|
|
{
|
|
int i;
|
|
edict_t *e;
|
|
|
|
for (i = 1; i <= maxclients->value; i++)
|
|
{
|
|
e = g_edicts + i;
|
|
if (e->inuse)
|
|
{
|
|
if (e->client->resp.team == whichteam)
|
|
{
|
|
gi.cprintf(e, PRINT_HIGH, "Your team now has %d more player%s than %s.\n",
|
|
uneven_amount, uneven_amount == 1 ? "" : "s", opponent);
|
|
stuffcmd(e, "play misc/comp_up.wav");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckForUnevenTeams()
|
|
{
|
|
int i, onteam1 = 0, onteam2 = 0;
|
|
edict_t *e;
|
|
|
|
// only use these messages during 2-team games...
|
|
if (num_teams > 2)
|
|
return;
|
|
|
|
for (i = 1; i <= maxclients->value; i++)
|
|
{
|
|
e = g_edicts + i;
|
|
if (e->inuse)
|
|
{
|
|
if (e->client->resp.team == TEAM1)
|
|
onteam1++;
|
|
else if (e->client->resp.team == TEAM2)
|
|
onteam2++;
|
|
}
|
|
}
|
|
if (onteam1 > onteam2)
|
|
UnevenTeamsMsg(TEAM1, onteam1 - onteam2, team2_name);
|
|
else if (onteam2 > onteam1)
|
|
UnevenTeamsMsg(TEAM2, onteam2 - onteam1, team1_name);
|
|
}
|
|
|
|
void JoinTeam(edict_t *ent, int desired_team, int skip_menuclose)
|
|
{
|
|
char *s, *a;
|
|
|
|
if (!skip_menuclose)
|
|
{
|
|
PMenu_Close(ent);
|
|
}
|
|
|
|
if (ent->client->resp.team == desired_team)
|
|
return;
|
|
|
|
a = (ent->client->resp.team == NOTEAM) ? "joined" : "changed to";
|
|
|
|
ent->client->resp.team = desired_team;
|
|
s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
|
|
AssignSkin(ent, s);
|
|
|
|
if (ent->solid != SOLID_NOT) // alive, in game
|
|
{
|
|
ent->health = 0;
|
|
player_die (ent, ent, ent, 100000, vec3_origin);
|
|
ent->deadflag = DEAD_DEAD;
|
|
}
|
|
|
|
gi.bprintf(PRINT_HIGH, "%s %s %s.\n",
|
|
ent->client->pers.netname, a, TeamName(desired_team));
|
|
|
|
ent->client->resp.joined_team = level.framenum;
|
|
|
|
CheckForUnevenTeams();
|
|
|
|
if (!skip_menuclose)
|
|
OpenWeaponMenu(ent);
|
|
}
|
|
|
|
void LeaveTeam(edict_t *ent)
|
|
{
|
|
char *g;
|
|
|
|
if (ent->client->resp.team == NOTEAM)
|
|
return;
|
|
|
|
if (ent->solid != SOLID_NOT) // alive, in game
|
|
{
|
|
ent->health = 0;
|
|
player_die (ent, ent, ent, 100000, vec3_origin);
|
|
ent->deadflag = DEAD_DEAD;
|
|
}
|
|
|
|
if (IsNeutral(ent))
|
|
g = "its";
|
|
else if (IsFemale(ent))
|
|
g = "her";
|
|
else
|
|
g = "his";
|
|
gi.bprintf(PRINT_HIGH, "%s left %s team.\n", ent->client->pers.netname, g);
|
|
|
|
ent->client->resp.joined_team = 0;
|
|
ent->client->resp.team = NOTEAM;
|
|
|
|
CheckForUnevenTeams();
|
|
}
|
|
|
|
void ReturnToMain(edict_t *ent, pmenu_t *p)
|
|
{
|
|
PMenu_Close(ent);
|
|
OpenJoinMenu(ent);
|
|
}
|
|
|
|
void OpenItemMenu(edict_t *ent)
|
|
{
|
|
PMenu_Open(ent, itemmenu, 4, sizeof(itemmenu) / sizeof(pmenu_t));
|
|
}
|
|
|
|
void OpenWeaponMenu(edict_t *ent)
|
|
{
|
|
PMenu_Open(ent, weapmenu, 4, sizeof(weapmenu) / sizeof(pmenu_t));
|
|
}
|
|
|
|
int UpdateJoinMenu(edict_t *ent)
|
|
{
|
|
static char levelname[32];
|
|
static char team1players[32];
|
|
static char team2players[32];
|
|
int num1, num2, i;
|
|
|
|
joinmenu[3].text = team1_name;
|
|
joinmenu[3].SelectFunc = JoinTeam1;
|
|
joinmenu[5].text = team2_name;
|
|
joinmenu[5].SelectFunc = JoinTeam2;
|
|
|
|
levelname[0] = '*';
|
|
if (g_edicts[0].message)
|
|
strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
|
|
else
|
|
strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
|
|
levelname[sizeof(levelname) - 1] = 0;
|
|
|
|
num1 = num2 = 0;
|
|
for (i = 0; i < maxclients->value; 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++;
|
|
}
|
|
|
|
sprintf(team1players, " (%d players)", num1);
|
|
sprintf(team2players, " (%d players)", num2);
|
|
|
|
joinmenu[1].text = levelname;
|
|
if (joinmenu[3].text)
|
|
joinmenu[4].text = team1players;
|
|
else
|
|
joinmenu[4].text = NULL;
|
|
if (joinmenu[5].text)
|
|
joinmenu[6].text = team2players;
|
|
else
|
|
joinmenu[6].text = NULL;
|
|
|
|
if (num1 > num2)
|
|
return TEAM2;
|
|
else if (num2 > num1)
|
|
return TEAM1;
|
|
else if (team1_score > team2_score)
|
|
return TEAM2;
|
|
else if (team2_score > team1_score)
|
|
return TEAM1;
|
|
else
|
|
return TEAM1;
|
|
}
|
|
|
|
void OpenJoinMenu(edict_t *ent)
|
|
{
|
|
int team;
|
|
|
|
team = UpdateJoinMenu(ent);
|
|
if (team == TEAM1)
|
|
team = 3;
|
|
else
|
|
team = 5;
|
|
PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t));
|
|
}
|
|
|
|
int member_array(char *str, char *arr[], int num_elems)
|
|
{
|
|
int l;
|
|
for (l = 0; l < num_elems; l++)
|
|
{
|
|
if (!strcmp(str, arr[l]))
|
|
return l;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CleanLevel()
|
|
{
|
|
char *remove_classnames[] =
|
|
{
|
|
"weapon_Mk23",
|
|
"weapon_MP5",
|
|
"weapon_M4",
|
|
"weapon_M3",
|
|
"weapon_HC",
|
|
"weapon_Sniper",
|
|
"weapon_Dual",
|
|
"weapon_Knife",
|
|
"weapon_Grenade",
|
|
"ammo_sniper",
|
|
"ammo_clip",
|
|
"ammo_mag",
|
|
"ammo_m4",
|
|
"ammo_m3",
|
|
"item_quiet",
|
|
"item_slippers",
|
|
"item_band",
|
|
"item_lasersight",
|
|
"item_vest",
|
|
"thrown_knife",
|
|
"hgrenade",
|
|
|
|
};
|
|
int i;
|
|
int base;
|
|
edict_t *ent;
|
|
|
|
base = 1 + maxclients->value + BODY_QUEUE_SIZE;
|
|
ent = g_edicts + base;
|
|
for (i = 1 + maxclients->value + BODY_QUEUE_SIZE;
|
|
i < globals.num_edicts;
|
|
i++, ent++)
|
|
{
|
|
if (!ent->classname)
|
|
continue;
|
|
if (member_array(ent->classname, remove_classnames,
|
|
sizeof(remove_classnames) / sizeof(char *)) > -1)
|
|
{
|
|
G_FreeEdict(ent);
|
|
}
|
|
}
|
|
|
|
CleanBodies();
|
|
|
|
// fix glass
|
|
CGF_SFX_RebuildAllBrokenGlass();
|
|
}
|
|
|
|
qboolean StartClient(edict_t *ent)
|
|
{
|
|
if (ent->client->resp.team != NOTEAM)
|
|
return false;
|
|
|
|
// start as 'observer'
|
|
ent->movetype = MOVETYPE_NOCLIP;
|
|
ent->solid = SOLID_NOT;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
ent->client->resp.team = NOTEAM;
|
|
ent->client->ps.gunindex = 0;
|
|
gi.linkentity (ent);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CenterPrintAll(char *msg)
|
|
{
|
|
int i;
|
|
edict_t *ent;
|
|
|
|
gi.cprintf(NULL, PRINT_HIGH, msg); // so it goes to the server console...
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse)
|
|
continue;
|
|
gi.centerprintf(ent, "%s", msg);
|
|
}
|
|
}
|
|
|
|
qboolean BothTeamsHavePlayers()
|
|
{
|
|
int onteam1 = 0, onteam2 = 0, i;
|
|
edict_t *ent;
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (game.clients[i].resp.team == TEAM1)
|
|
onteam1++;
|
|
else if (game.clients[i].resp.team == TEAM2)
|
|
onteam2++;
|
|
}
|
|
|
|
return (onteam1 > 0 && onteam2 > 0);
|
|
}
|
|
|
|
// CheckForWinner: Checks for a winner (or not).
|
|
int CheckForWinner()
|
|
{
|
|
int onteam1 = 0, onteam2 = 0, i;
|
|
edict_t *ent;
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (game.clients[i].resp.team == TEAM1 &&
|
|
ent->solid != SOLID_NOT)
|
|
onteam1++;
|
|
else if (game.clients[i].resp.team == TEAM2 &&
|
|
ent->solid != SOLID_NOT)
|
|
onteam2++;
|
|
}
|
|
|
|
if (onteam1 > 0 && onteam2 > 0)
|
|
return WINNER_NONE;
|
|
else if (onteam1 == 0 && onteam2 == 0)
|
|
return WINNER_TIE;
|
|
else if (onteam1 > 0 && onteam2 == 0)
|
|
return WINNER_TEAM1;
|
|
else
|
|
return WINNER_TEAM2;
|
|
}
|
|
|
|
// CheckForForcedWinner: A winner is being forced, find who it is.
|
|
int CheckForForcedWinner()
|
|
{
|
|
int onteam1 = 0, onteam2 = 0, i;
|
|
int health1 = 0, health2 = 0;
|
|
edict_t *ent;
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (game.clients[i].resp.team == TEAM1 && ent->solid != SOLID_NOT)
|
|
{
|
|
onteam1++;
|
|
health1 += ent->health;
|
|
}
|
|
else if (game.clients[i].resp.team == TEAM2 && ent->solid != SOLID_NOT)
|
|
{
|
|
onteam2++;
|
|
health2 += ent->health;
|
|
}
|
|
}
|
|
|
|
if (onteam1 > onteam2)
|
|
{
|
|
return WINNER_TEAM1;
|
|
}
|
|
else if (onteam2 > onteam1)
|
|
{
|
|
return WINNER_TEAM2;
|
|
}
|
|
else
|
|
{
|
|
if (health1 > health2)
|
|
return WINNER_TEAM1;
|
|
else if (health2 > health1)
|
|
return WINNER_TEAM2;
|
|
else
|
|
return WINNER_TIE;
|
|
}
|
|
}
|
|
|
|
void SpawnPlayers()
|
|
{
|
|
int i;
|
|
edict_t *ent;
|
|
|
|
GetSpawnPoints();
|
|
SetupTeamSpawnPoints();
|
|
InitTransparentList();
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (ent->inuse && ent->client->resp.team != NOTEAM)
|
|
{
|
|
PutClientInServer(ent);
|
|
AddToTransparentList(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartRound()
|
|
{
|
|
team_round_going = 1;
|
|
current_round_length = 0;
|
|
}
|
|
|
|
void StartLCA()
|
|
{
|
|
CleanLevel();
|
|
|
|
CenterPrintAll("LIGHTS...\n");
|
|
gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD,
|
|
gi.soundindex("atl/lights.wav"), 1.0, ATTN_NONE, 0.0);
|
|
lights_camera_action = 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
|
|
|| other->solid == SOLID_NOT
|
|
|| other->deadflag == DEAD_DEAD)
|
|
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 == 21)
|
|
{
|
|
CenterPrintAll("CAMERA...\n");
|
|
gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD,
|
|
gi.soundindex("atl/camera.wav"), 1.0, ATTN_NONE, 0.0);
|
|
}
|
|
else if (lights_camera_action == 1)
|
|
{
|
|
CenterPrintAll("ACTION!\n");
|
|
gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD,
|
|
gi.soundindex("atl/action.wav"), 1.0, ATTN_NONE, 0.0);
|
|
StartRound();
|
|
}
|
|
lights_camera_action--;
|
|
}
|
|
|
|
void MakeAllLivePlayersObservers()
|
|
{
|
|
edict_t *ent;
|
|
int saveteam, i;
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[1+i];
|
|
if (!ent->inuse || ent->solid == SOLID_NOT)
|
|
continue;
|
|
saveteam = ent->client->resp.team;
|
|
ent->client->resp.team = NOTEAM;
|
|
PutClientInServer(ent);
|
|
ent->client->resp.team = saveteam;
|
|
}
|
|
}
|
|
|
|
// WonGame: returns true if we're exiting the level.
|
|
int WonGame(int winner)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "The round is over:\n");
|
|
|
|
if (winner == WINNER_TIE)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "It was a tie, no points awarded!\n");
|
|
}
|
|
else
|
|
{
|
|
if (winner == WINNER_TEAM1)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM1));
|
|
team1_score++;
|
|
}
|
|
else
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM2));
|
|
team2_score++;
|
|
}
|
|
}
|
|
|
|
if (timelimit->value)
|
|
{
|
|
if (level.time >= timelimit->value*60)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "Timelimit hit.\n");
|
|
EndDMLevel();
|
|
team_round_going = team_round_countdown = team_game_going = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (roundlimit->value)
|
|
{
|
|
if (team1_score >= roundlimit->value || team2_score >= roundlimit->value)
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "Roundlimit hit.\n");
|
|
EndDMLevel();
|
|
team_round_going = team_round_countdown = team_game_going = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void CheckTeamRules()
|
|
{
|
|
int winner;
|
|
int checked_tie = 0;
|
|
|
|
if (lights_camera_action)
|
|
{
|
|
ContinueLCA();
|
|
return;
|
|
}
|
|
|
|
if (team_round_going)
|
|
current_round_length++;
|
|
|
|
if (holding_on_tie_check)
|
|
{
|
|
holding_on_tie_check--;
|
|
if (holding_on_tie_check > 0)
|
|
return;
|
|
holding_on_tie_check = 0;
|
|
checked_tie = 1;
|
|
}
|
|
|
|
if (team_round_countdown == 1)
|
|
{
|
|
team_round_countdown = 0;
|
|
if (BothTeamsHavePlayers())
|
|
{
|
|
team_game_going = 1;
|
|
StartLCA();
|
|
}
|
|
else
|
|
{
|
|
CenterPrintAll("Not enough players to play!\n");
|
|
MakeAllLivePlayersObservers();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (team_round_countdown)
|
|
team_round_countdown--;
|
|
}
|
|
|
|
// check these rules every 1.5 seconds...
|
|
rulecheckfrequency++;
|
|
if (rulecheckfrequency % 15 && !checked_tie)
|
|
return;
|
|
|
|
if (!team_round_going)
|
|
{
|
|
if (timelimit->value)
|
|
{
|
|
if (level.time >= timelimit->value*60)
|
|
{
|
|
gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
|
|
EndDMLevel();
|
|
team_round_going = team_round_countdown = team_game_going = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!team_round_countdown)
|
|
{
|
|
if (BothTeamsHavePlayers())
|
|
{
|
|
CenterPrintAll("The round will begin in 20 seconds!\n");
|
|
team_round_countdown = 201;
|
|
}
|
|
}
|
|
}
|
|
else /* team_round_going */
|
|
{
|
|
if ((winner = CheckForWinner()) != WINNER_NONE)
|
|
{
|
|
if (!checked_tie)
|
|
{
|
|
holding_on_tie_check = 50;
|
|
return;
|
|
}
|
|
if (WonGame(winner))
|
|
return;
|
|
team_round_going = 0;
|
|
lights_camera_action = 0;
|
|
holding_on_tie_check = 0;
|
|
team_round_countdown = 71;
|
|
return;
|
|
}
|
|
|
|
if (roundtimelimit->value &&
|
|
(current_round_length > roundtimelimit->value * 600))
|
|
{
|
|
gi.bprintf(PRINT_HIGH, "Round timelimit hit.\n");
|
|
winner = CheckForForcedWinner();
|
|
if (WonGame(winner))
|
|
return;
|
|
team_round_going = 0;
|
|
lights_camera_action = 0;
|
|
holding_on_tie_check = 0;
|
|
team_round_countdown = 71;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void A_Scoreboard(edict_t *ent)
|
|
{
|
|
if (ent->client->showscores && ent->client->scoreboardnum == 1)
|
|
{
|
|
// blink header of the winning team during intermission
|
|
if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
|
|
if (team1_score > team2_score)
|
|
ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
|
|
else if (team2_score > team1_score)
|
|
ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
|
|
else if (team1_total > team2_total) // frag tie breaker
|
|
ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
|
|
else if (team2_total > team1_total)
|
|
ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
|
|
else { // tie game!
|
|
ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
|
|
ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.stats[STAT_TEAM1_PIC] = gi.imageindex(team1_skin_index);
|
|
ent->client->ps.stats[STAT_TEAM2_PIC] = gi.imageindex(team2_skin_index);
|
|
}
|
|
|
|
ent->client->ps.stats[STAT_TEAM1_SCORE] = team1_score;
|
|
ent->client->ps.stats[STAT_TEAM2_SCORE] = team2_score;
|
|
}
|
|
}
|
|
|
|
// Maximum number of lines of scores to put under each team's header.
|
|
#define MAX_SCORES_PER_TEAM 9
|
|
|
|
void A_ScoreboardMessage (edict_t *ent, edict_t *killer)
|
|
{
|
|
char string[1400], damage[50];
|
|
gclient_t *cl;
|
|
edict_t *cl_ent;
|
|
int maxsize = 1000, i, j, k;
|
|
|
|
if (ent->client->scoreboardnum == 1)
|
|
{
|
|
int team, len, deadview;
|
|
int sorted[TEAM_TOP][MAX_CLIENTS];
|
|
int sortedscores[TEAM_TOP][MAX_CLIENTS];
|
|
int score, total[TEAM_TOP], totalscore[TEAM_TOP];
|
|
int totalalive[TEAM_TOP], totalaliveprinted[TEAM_TOP];
|
|
int stoppedat[TEAM_TOP];
|
|
int name_pos[TEAM_TOP];
|
|
|
|
deadview = (ent->solid == SOLID_NOT ||
|
|
ent->deadflag == DEAD_DEAD ||
|
|
!team_round_going);
|
|
|
|
ent->client->ps.stats[STAT_TEAM_HEADER] = gi.imageindex ("tag3");
|
|
|
|
total[TEAM1] = total[TEAM2] = totalalive[TEAM1] = totalalive[TEAM2] =
|
|
totalscore[TEAM1] = totalscore[TEAM2] = 0;
|
|
|
|
for (i=0 ; i<game.maxclients ; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
|
|
if (game.clients[i].resp.team == NOTEAM)
|
|
continue;
|
|
else
|
|
team = game.clients[i].resp.team;
|
|
|
|
score = game.clients[i].resp.score;
|
|
if (noscore->value)
|
|
{
|
|
j = total[team];
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < total[team]; j++)
|
|
{
|
|
if (score > sortedscores[team][j])
|
|
break;
|
|
}
|
|
for (k=total[team] ; k>j ; k--)
|
|
{
|
|
sorted[team][k] = sorted[team][k-1];
|
|
sortedscores[team][k] = sortedscores[team][k-1];
|
|
}
|
|
}
|
|
sorted[team][j] = i;
|
|
sortedscores[team][j] = score;
|
|
totalscore[team] += score;
|
|
total[team]++;
|
|
if (cl_ent->solid != SOLID_NOT &&
|
|
cl_ent->deadflag != DEAD_DEAD)
|
|
totalalive[team]++;
|
|
}
|
|
|
|
// I've shifted the scoreboard position 8 pixels to the left in Axshun so it works
|
|
// correctly in 320x240 (Action's does not)--any problems with this? -FB
|
|
// Also going to center the team names.
|
|
|
|
name_pos[TEAM1] = ((20 - strlen(team1_name)) / 2) * 8;
|
|
if (name_pos[TEAM1] < 0)
|
|
name_pos[TEAM1] = 0;
|
|
name_pos[TEAM2] = ((20 - strlen(team2_name)) / 2) * 8;
|
|
if (name_pos[TEAM2] < 0)
|
|
name_pos[TEAM2] = 0;
|
|
|
|
sprintf(string,
|
|
// TEAM1
|
|
"if 24 xv 0 yv 8 pic 24 endif "
|
|
"if 22 xv 32 yv 8 pic 22 endif "
|
|
"xv 32 yv 28 string \"%4d/%-3d\" "
|
|
"xv 90 yv 12 num 2 26 "
|
|
"xv %d yv 0 string \"%s\" "
|
|
// TEAM2
|
|
"if 25 xv 160 yv 8 pic 25 endif "
|
|
"if 22 xv 192 yv 8 pic 22 endif "
|
|
"xv 192 yv 28 string \"%4d/%-3d\" "
|
|
"xv 248 yv 12 num 2 27 "
|
|
"xv %d yv 0 string \"%s\" ",
|
|
totalscore[TEAM1], total[TEAM1], name_pos[TEAM1], team1_name,
|
|
totalscore[TEAM2], total[TEAM2], name_pos[TEAM2] + 160, team2_name);
|
|
|
|
len = strlen(string);
|
|
|
|
totalaliveprinted[TEAM1] = totalaliveprinted[TEAM2] = 0;
|
|
stoppedat[TEAM1] = stoppedat[TEAM2] = -1;
|
|
|
|
for (i=0 ; i < (MAX_SCORES_PER_TEAM + 1) ; i++)
|
|
{
|
|
if (i >= total[TEAM1] && i >= total[TEAM2])
|
|
break;
|
|
|
|
// ok, if we're approaching the "maxsize", then let's stop printing members of each
|
|
// teams (if there's more than one member left to print in that team...)
|
|
if (len > (maxsize - 100))
|
|
{
|
|
if (i < (total[TEAM1] - 1))
|
|
stoppedat[TEAM1] = i;
|
|
if (i < (total[TEAM2] - 1))
|
|
stoppedat[TEAM2] = i;
|
|
}
|
|
if (i == MAX_SCORES_PER_TEAM-1)
|
|
{
|
|
if (total[TEAM1] > MAX_SCORES_PER_TEAM)
|
|
stoppedat[TEAM1] = i;
|
|
if (total[TEAM2] > MAX_SCORES_PER_TEAM)
|
|
stoppedat[TEAM2] = i;
|
|
}
|
|
|
|
if (i < total[TEAM1] && stoppedat[TEAM1] == -1) // print next team 1 member...
|
|
{
|
|
cl = &game.clients[sorted[TEAM1][i]];
|
|
cl_ent = g_edicts + 1 + sorted[TEAM1][i];
|
|
if (cl_ent->solid != SOLID_NOT &&
|
|
cl_ent->deadflag != DEAD_DEAD)
|
|
totalaliveprinted[TEAM1]++;
|
|
|
|
// AQ truncates names at 12, not sure why, except maybe to conserve scoreboard
|
|
// string space? skipping that "feature". -FB
|
|
|
|
sprintf(string+strlen(string),
|
|
"xv 0 yv %d string%s \"%s\" ",
|
|
42 + i * 8,
|
|
deadview ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "",
|
|
game.clients[sorted[TEAM1][i]].pers.netname);
|
|
}
|
|
|
|
if (i < total[TEAM2] && stoppedat[TEAM2] == -1) // print next team 2 member...
|
|
{
|
|
cl = &game.clients[sorted[TEAM2][i]];
|
|
cl_ent = g_edicts + 1 + sorted[TEAM2][i];
|
|
if (cl_ent->solid != SOLID_NOT &&
|
|
cl_ent->deadflag != DEAD_DEAD)
|
|
totalaliveprinted[TEAM2]++;
|
|
|
|
// AQ truncates names at 12, not sure why, except maybe to conserve scoreboard
|
|
// string space? skipping that "feature". -FB
|
|
|
|
sprintf(string+strlen(string),
|
|
"xv 160 yv %d string%s \"%s\" ",
|
|
42 + i * 8,
|
|
deadview ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "",
|
|
game.clients[sorted[TEAM2][i]].pers.netname);
|
|
}
|
|
|
|
len = strlen(string);
|
|
}
|
|
|
|
// Print remaining players if we ran out of room...
|
|
if (!deadview) // live player viewing scoreboard...
|
|
{
|
|
if (stoppedat[TEAM1] > -1)
|
|
{
|
|
sprintf(string + strlen(string), "xv 0 yv %d string \"..and %d more\" ",
|
|
42 + (stoppedat[TEAM1] * 8), total[TEAM1] - stoppedat[TEAM1]);
|
|
}
|
|
if (stoppedat[TEAM2] > -1)
|
|
{
|
|
sprintf(string + strlen(string), "xv 160 yv %d string \"..and %d more\" ",
|
|
42 + (stoppedat[TEAM2] * 8), total[TEAM2] - stoppedat[TEAM2]);
|
|
}
|
|
}
|
|
else // dead player viewing scoreboard...
|
|
{
|
|
if (stoppedat[TEAM1] > -1)
|
|
{
|
|
sprintf(string + strlen(string), "xv 0 yv %d string%s \"..and %d/%d more\" ",
|
|
42 + (stoppedat[TEAM1] * 8),
|
|
(totalalive[TEAM1] - totalaliveprinted[TEAM1]) ? "2" : "",
|
|
totalalive[TEAM1] - totalaliveprinted[TEAM1],
|
|
total[TEAM1] - stoppedat[TEAM1]);
|
|
}
|
|
if (stoppedat[TEAM2] > -1)
|
|
{
|
|
sprintf(string + strlen(string), "xv 160 yv %d string%s \"..and %d/%d more\" ",
|
|
42 + (stoppedat[TEAM2] * 8),
|
|
(totalalive[TEAM2] - totalaliveprinted[TEAM2]) ? "2" : "",
|
|
totalalive[TEAM2] - totalaliveprinted[TEAM2],
|
|
total[TEAM2] - stoppedat[TEAM2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (ent->client->scoreboardnum == 2)
|
|
|
|
{
|
|
int total, score, ping;
|
|
int sortedscores[MAX_CLIENTS], sorted[MAX_CLIENTS];
|
|
|
|
total = score = 0;
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
|
|
score = game.clients[i].resp.score;
|
|
if (noscore->value)
|
|
{
|
|
j = total;
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < total; j++)
|
|
{
|
|
if (score > sortedscores[j])
|
|
break;
|
|
}
|
|
for (k = total ; k > j ; k--)
|
|
{
|
|
sorted[k] = sorted[k-1];
|
|
sortedscores[k] = sortedscores[k-1];
|
|
}
|
|
}
|
|
sorted[j] = i;
|
|
sortedscores[j] = score;
|
|
total++;
|
|
}
|
|
|
|
if (noscore->value)
|
|
{
|
|
strcpy(string, "xv 0 yv 32 string2 \"Player Time Ping\" "
|
|
"xv 0 yv 40 string2 \"--------------- ---- ----\" ");
|
|
}
|
|
else
|
|
{
|
|
strcpy(string, "xv 0 yv 32 string2 \"Frags Player Time Ping Damage\" "
|
|
"xv 0 yv 40 string2 \"----- --------------- ---- ---- ------\" ");
|
|
}
|
|
|
|
for (i = 0; i < total; i++)
|
|
{
|
|
ping = game.clients[sorted[i]].ping;
|
|
if (ping > 999)
|
|
ping = 999;
|
|
if (noscore->value)
|
|
{
|
|
sprintf(string + strlen(string),
|
|
"xv 0 yv %d string \"%-15s %4d %4d\" ",
|
|
48 + i * 8,
|
|
game.clients[sorted[i]].pers.netname,
|
|
(level.framenum - game.clients[sorted[i]].resp.enterframe)/600,
|
|
ping);
|
|
}
|
|
else
|
|
{
|
|
if (game.clients[sorted[i]].resp.damage_dealt < 1000000)
|
|
sprintf(damage, "%d", game.clients[sorted[i]].resp.damage_dealt);
|
|
else
|
|
strcpy(damage, "******");
|
|
sprintf(string + strlen(string),
|
|
"xv 0 yv %d string \"%5d %-15s %4d %4d %6s\" ",
|
|
48 + i * 8,
|
|
sortedscores[i],
|
|
game.clients[sorted[i]].pers.netname,
|
|
(level.framenum - game.clients[sorted[i]].resp.enterframe)/600,
|
|
ping, damage);
|
|
}
|
|
|
|
if (strlen(string) > (maxsize - 100) &&
|
|
i < (total - 2))
|
|
{
|
|
sprintf(string + strlen(string),
|
|
"xv 0 yv %d string \"..and %d more\" ",
|
|
48 + (i + 1) * 8,
|
|
(total - i - 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (strlen(string) > 1300) // for debugging...
|
|
gi.dprintf("Warning: scoreboard string neared or exceeded max length\nDump:\n%s\n---\n",
|
|
string);
|
|
|
|
gi.WriteByte (svc_layout);
|
|
gi.WriteString (string);
|
|
}
|
|
|
|
// called when we enter the intermission
|
|
void TallyEndOfLevelTeamScores(void)
|
|
{
|
|
int i;
|
|
|
|
team1_total = team2_total = 0;
|
|
for (i = 0; i < maxclients->value; i++)
|
|
{
|
|
if (!g_edicts[i+1].inuse)
|
|
continue;
|
|
if (game.clients[i].resp.team == TEAM1)
|
|
team1_total += game.clients[i].resp.score;
|
|
else if (game.clients[i].resp.team == TEAM2)
|
|
team2_total += game.clients[i].resp.score;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
vec3_t v;
|
|
VectorSubtract (spot1->s.origin, spot2->s.origin, v);
|
|
return VectorLength(v);
|
|
}
|
|
|
|
// GetSpawnPoints:
|
|
// Put the spawn points into our potential_spawns array so we can work with them easily.
|
|
void GetSpawnPoints()
|
|
{
|
|
edict_t *spot;
|
|
|
|
spot = NULL;
|
|
num_potential_spawns = 0;
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
|
|
{
|
|
potential_spawns[num_potential_spawns] = spot;
|
|
num_potential_spawns++;
|
|
if (num_potential_spawns >= MAX_SPAWNS)
|
|
{
|
|
gi.dprintf("Warning: MAX_SPAWNS exceeded\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// newrand returns n, where 0 >= n < top
|
|
int newrand(int top)
|
|
{
|
|
return (int)(random() * top);
|
|
}
|
|
|
|
// 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, qboolean teams_assigned[])
|
|
{
|
|
int spawn_point;
|
|
spawn_point = newrand(num_potential_spawns);
|
|
teamplay_spawns[team] = potential_spawns[spawn_point];
|
|
teams_assigned[team] = true;
|
|
}
|
|
|
|
void SelectFarTeamplaySpawnPoint(int team, qboolean teams_assigned[])
|
|
{
|
|
int x, y, spawn_to_use, preferred_spawn_points, num_already_used,
|
|
total_good_spawn_points;
|
|
float closest_spawn_distance, distance;
|
|
spawn_distances_t *spawn_distances;
|
|
|
|
spawn_distances = (spawn_distances_t *)gi.TagMalloc(num_potential_spawns * sizeof(spawn_distances_t), TAG_GAME);
|
|
|
|
num_already_used = 0;
|
|
for (x = 0; x < num_potential_spawns; x++)
|
|
{
|
|
closest_spawn_distance = 2000000000;
|
|
|
|
for (y = 0; y < num_teams; 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 ((int)dmflags->value & DF_SPAWN_FARTHEST)
|
|
preferred_spawn_points = 1;
|
|
//FB 6/1/99
|
|
|
|
spawn_to_use = newrand(preferred_spawn_points);
|
|
|
|
if (team < 0 || team >= MAX_TEAMS)
|
|
{
|
|
gi.dprintf("Out-of-range teams value in SelectFarTeamplaySpawnPoint, skipping...\n");
|
|
}
|
|
else
|
|
{
|
|
teams_assigned[team] = true;
|
|
teamplay_spawns[team] = spawn_distances[num_potential_spawns - spawn_to_use - 1].s;
|
|
}
|
|
|
|
gi.TagFree(spawn_distances);
|
|
}
|
|
|
|
// SetupTeamSpawnPoints:
|
|
//
|
|
// Setup the points at which the teams will spawn.
|
|
//
|
|
void SetupTeamSpawnPoints()
|
|
{
|
|
qboolean teams_assigned[MAX_TEAMS];
|
|
int started_at, l, firstassignment;
|
|
|
|
for (l = 0; l < num_teams; l++)
|
|
{
|
|
teamplay_spawns[l] = NULL;
|
|
teams_assigned[l] = false;
|
|
}
|
|
|
|
started_at = l = newrand(num_teams);
|
|
firstassignment = 1;
|
|
do
|
|
{
|
|
if (l < 0 || l >= MAX_TEAMS)
|
|
{
|
|
gi.dprintf("Warning: attempt to setup spawns for out-of-range team (%d)\n", l);
|
|
}
|
|
|
|
if (firstassignment)
|
|
{
|
|
SelectRandomTeamplaySpawnPoint(l, teams_assigned);
|
|
firstassignment = 0;
|
|
}
|
|
else
|
|
{
|
|
SelectFarTeamplaySpawnPoint(l, teams_assigned);
|
|
}
|
|
l++;
|
|
if (l == num_teams)
|
|
l = 0;
|
|
} while (l != started_at);
|
|
}
|