thirtyflightsofloving/awaken2/g_team.c

5768 lines
134 KiB
C
Raw Normal View History

// g_team.c
#include "g_local.h"
#include "m_player.h"
cvar_t *ctf_forcejoin;
cvar_t *matchlock;
cvar_t *matchtime;
cvar_t *matchsetuptime;
cvar_t *warn_unbalanced;
// Index for various CTF pics, this saves us from calling gi.imageindex
// all the time and saves a few CPU cycles since we don't have to do
// a bunch of string compares all the time.
// These are set in CTFPrecache() called from worldspawn
int imageindex_i_ctf1;
int imageindex_i_ctf2;
int imageindex_i_ctf1d;
int imageindex_i_ctf2d;
int imageindex_i_ctf1t;
int imageindex_i_ctf2t;
int imageindex_i_ctfj;
int imageindex_sbfctf1;
int imageindex_sbfctf2;
int imageindex_ctfsb1;
int imageindex_ctfsb2;
int imageindex_i_aslta; //CW++
char *ctf_statusbar =
"yb -24 "
// health
"xv 0 "
"hnum "
"xv 50 "
"pic 0 "
// ammo
"if 2 "
" xv 100 "
" anum "
" xv 150 "
" pic 2 "
"endif "
// armor
"if 4 "
" xv 200 "
" rnum "
" xv 250 "
" pic 4 "
"endif "
// selected item
"if 6 "
" xv 296 "
" pic 6 "
"endif "
"yb -50 "
// picked up item
"if 7 "
" xv 0 "
" pic 7 "
" xv 26 "
" yb -42 "
" stat_string 8 "
" yb -50 "
"endif "
// timer
"if 9 "
"xv 246 "
"num 3 10 " //CW
"xv 296 "
"pic 9 "
"endif "
// help / weapon icon
"if 11 "
"xv 150 " //CW
"pic 11 "
"endif "
// frags
"xr -50 "
"yt 2 "
"num 3 14 "
// tech
"yb -129 "
"if 26 "
"xr -26 "
"pic 26 "
"endif "
// red team
"yb -102 "
"if 17 "
"xr -26 "
"pic 17 "
"endif "
"xr -62 "
"num 2 18 "
// joined overlay
"if 22 "
"yb -104 "
"xr -28 "
"pic 22 "
"endif "
// blue team
"yb -75 "
"if 19 "
"xr -26 "
"pic 19 "
"endif "
"xr -62 "
"num 2 20 "
"if 23 "
"yb -77 "
"xr -28 "
"pic 23 "
"endif "
// have flag graph
"if 21 "
"yt 26 "
"xr -24 "
"pic 21 "
"endif "
// id view state
"if 27 "
"xv 112 "
"yb -58 "
"stat_string 27 "
"endif "
"if 29 "
"xv 96 "
"yb -58 "
"pic 29 "
"endif "
"if 28 "
"xl 0 "
"yb -78 "
"stat_string 28 "
"endif "
"if 30 "
"xl 0 "
"yb -88 "
"stat_string 30 "
"endif "
;
static char *tnames[] = {
"item_tech1", "item_tech2", "item_tech3", "item_tech4",
NULL
};
void StuffCmd(edict_t *ent, char *s)
{
//Maj++
// don't unicast() to bots!
if (ent->isabot)
return;
//Maj--
gi.WriteByte(11);
gi.WriteString(s);
gi.unicast(ent, true);
}
//CW++
void StuffCmd_Ent(edict_t *self)
{
if (!self->target_ent)
{
gi.dprintf("BUG: StuffCmd_Ent() called for a null target_ent.\nPlease contact musashi@planetquake.com\n");
G_FreeEdict(self);
return;
}
if (!self->target_ent->client)
{
gi.dprintf("BUG: StuffCmd_Ent() called for a null client.\nPlease contact musashi@planetquake.com\n");
G_FreeEdict(self);
return;
}
if (!self->target_ent->inuse)
{
G_FreeEdict(self);
return;
}
if (!self->message)
{
G_FreeEdict(self);
return;
}
StuffCmd(self->target_ent, self->message);
G_FreeEdict(self);
}
//CW--
/*--------------------------------------------------------------------------*/
/*
=================
findradius
Returns entities whose bounding boxes are within
a spherical volume centred on the specified point.
=================
*/
static edict_t *loc_findradius(edict_t *from, vec3_t org, float rad)
{
vec3_t eorg;
int i;
if (!from)
from = g_edicts;
else
from++;
for ( ; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
continue;
for (i = 0; i < 3; ++i)
eorg[i] = org[i] - (from->s.origin[i] + (from->mins[i] + from->maxs[i])*0.5);
if (VectorLength(eorg) > rad)
continue;
return from;
}
return NULL;
}
static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
{
VectorAdd(org, mins, p[0]);
VectorCopy(p[0], p[1]);
p[1][0] -= mins[0];
VectorCopy(p[0], p[2]);
p[2][1] -= mins[1];
VectorCopy(p[0], p[3]);
p[3][0] -= mins[0];
p[3][1] -= mins[1];
VectorAdd(org, maxs, p[4]);
VectorCopy(p[4], p[5]);
p[5][0] -= maxs[0];
VectorCopy(p[0], p[6]);
p[6][1] -= maxs[1];
VectorCopy(p[0], p[7]);
p[7][0] -= maxs[0];
p[7][1] -= maxs[1];
}
static qboolean loc_CanSee(edict_t *targ, edict_t *inflictor)
{
trace_t trace;
vec3_t targpoints[8];
vec3_t viewpoint;
int i;
// bmodels need special checking because their origin is 0,0,0
if (targ->movetype == MOVETYPE_PUSH)
return false; // bmodels not supported
loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
VectorCopy(inflictor->s.origin, viewpoint);
viewpoint[2] += inflictor->viewheight;
for (i = 0; i < 8; i++)
{
trace = gi.trace(viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
if (trace.fraction == 1.0)
return true;
}
return false;
}
/*--------------------------------------------------------------------------*/
static gitem_t *flag1_item;
static gitem_t *flag2_item;
void CTFSpawn(void)
{
if (!flag1_item)
flag1_item = FindItemByClassname("item_flag_team1");
if (!flag2_item)
flag2_item = FindItemByClassname("item_flag_team2");
memset(&teamgame, 0, sizeof(teamgame));
CTFSetupTechSpawn();
if ((int)competition->value > 1)
{
teamgame.match = MATCH_SETUP;
teamgame.matchtime = level.time + (matchsetuptime->value * 60.0);
}
}
void CTFInit(void)
{
ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0);
competition = gi.cvar("competition", "0", 0); //CW
matchlock = gi.cvar("matchlock", "1", 0); //CW
matchtime = gi.cvar("matchtime", "20", 0); //CW
matchsetuptime = gi.cvar("matchsetuptime", "10", 0);
matchstarttime = gi.cvar("matchstarttime", "20", 0);
warn_unbalanced = gi.cvar("warn_unbalanced", "1", 0);
}
/*
* Precache CTF items
*/
void CTFPrecache(void)
{
imageindex_i_ctf1 = gi.imageindex("i_ctf1");
imageindex_i_ctf2 = gi.imageindex("i_ctf2");
imageindex_i_ctf1d = gi.imageindex("i_ctf1d");
imageindex_i_ctf2d = gi.imageindex("i_ctf2d");
imageindex_i_ctf1t = gi.imageindex("i_ctf1t");
imageindex_i_ctf2t = gi.imageindex("i_ctf2t");
imageindex_i_ctfj = gi.imageindex("i_ctfj");
imageindex_sbfctf1 = gi.imageindex("sbfctf1");
imageindex_sbfctf2 = gi.imageindex("sbfctf2");
imageindex_ctfsb1 = gi.imageindex("ctfsb1");
imageindex_ctfsb2 = gi.imageindex("ctfsb2");
}
/*--------------------------------------------------------------------------*/
char *CTFTeamName(int team)
{
switch (team)
{
case CTF_TEAM1:
return sv_team1_name->string; //CW
case CTF_TEAM2:
return sv_team2_name->string; //CW
}
return "UNKNOWN";
}
char *CTFOtherTeamName(int team)
{
switch (team)
{
case CTF_TEAM1:
return sv_team2_name->string; //CW
case CTF_TEAM2:
return sv_team1_name->string; //CW
}
return "UNKNOWN";
}
int CTFOtherTeam(int team)
{
switch (team)
{
case CTF_TEAM1:
return CTF_TEAM2;
case CTF_TEAM2:
return CTF_TEAM1;
}
return -1; // invalid value
}
/*--------------------------------------------------------------------------*/
edict_t *SelectRandomDeathmatchSpawnPoint(void);
edict_t *SelectFarthestDeathmatchSpawnPoint(void);
float PlayersRangeFromSpot(edict_t *spot);
float TeamPlayersRangeFromSpot(edict_t *spot, ctfteam_t team); //CW++
void CTFAssignSkin(edict_t *ent, char *s)
{ //r1,CW modified heavily throughout to prevent potential configstring overflows
int playernum = ent - g_edicts - 1;
char t[MAX_SKINLEN];
// Assign team models if the appropriate cvars are set, otherwise use "male" as the default.
if (strlen(sv_team1_model->string) && (ent->client->resp.ctf_team == CTF_TEAM1))
{
strncpy(t, sv_team1_model->string, sizeof(t)-2);
Com_strcat(t, sizeof(t), "/");
}
else if (strlen(sv_team2_model->string) && (ent->client->resp.ctf_team == CTF_TEAM2))
{
strncpy(t, sv_team2_model->string, sizeof(t)-2);
Com_strcat(t, sizeof(t), "/");
}
else
Com_strcpy(t, sizeof(t), "male/");
// Assign team skins if the appropriate cvars are set, otherwise use "ctf_r" and "ctf_b" as the defaults.
switch (ent->client->resp.ctf_team)
{
case CTF_TEAM1:
if (strlen(sv_team1_skin->string))
{
if (strlen(ent->client->pers.netname) + strlen(t) + strlen(sv_team1_skin->string) + 4 > MAX_SKINLEN - 1)
{
gi.dprintf("Team 1 model/skin name is too long.\n");
gi_cprintf(ent, PRINT_HIGH, "Team 1 model/skin name is too long.\n");
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, "male/grunt"));
}
else
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s%s", ent->client->pers.netname, t, sv_team1_skin->string));
}
else
{
if (strlen(ent->client->pers.netname) + strlen(t) + strlen(CTF_TEAM1_SKIN) + 4 > MAX_SKINLEN - 1)
{
gi.dprintf("Team 1 model/skin name is too long.\n");
gi_cprintf(ent, PRINT_HIGH, "Team 1 model/skin name is too long.\n");
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, "male/ctf_r"));
}
else
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM1_SKIN));
}
break;
case CTF_TEAM2:
if (strlen(sv_team2_skin->string))
{
if (strlen(ent->client->pers.netname) + strlen(t) + strlen(sv_team2_skin->string) + 4 > MAX_SKINLEN - 1)
{
gi.dprintf("Team 2 model/skin name is too long.\n");
gi_cprintf(ent, PRINT_HIGH, "Team 2 model/skin name is too long.\n");
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, "male/ctf_b"));
}
else
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s%s", ent->client->pers.netname, t, sv_team2_skin->string));
}
else
{
if (strlen(ent->client->pers.netname) + strlen(t) + strlen(CTF_TEAM2_SKIN) + 4 > MAX_SKINLEN - 1)
{
gi.dprintf("Team 2 model/skin name is too long.\n");
gi_cprintf(ent, PRINT_HIGH, "Team 2 model/skin name is too long.\n");
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, "male/ctf_b"));
}
else
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN));
}
break;
default:
if (strlen(ent->client->pers.netname) + strlen(s) + 4 > MAX_SKINLEN - 1)
{
gi_cprintf(ent, PRINT_HIGH, "Skin name is too long.\n");
s = "male/grunt";
}
gi.configstring(CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s));
break;
}
//CW--
}
void CTFAssignTeam(gclient_t *who)
{
edict_t *player;
int team1count = 0;
int team2count = 0;
int i;
//CW++
edict_t *ent;
qboolean isabot = false;
for (i = 1; i <= (int)maxclients->value; i++)
{
ent = &g_edicts[i];
if (ent->client == who)
{
if (ent->isabot)
isabot = true;
break;
}
}
//CW--
who->resp.ctf_state = 0;
if (!((int)dmflags->value & DF_FORCEJOIN) && !isabot)
{
who->resp.ctf_team = CTF_NOTEAM;
return;
}
//CW++
if (sv_gametype->value == G_FFA)
{
who->resp.ctf_team = CTF_TEAM_FFA;
return;
}
//CW--
for (i = 1; i <= (int)maxclients->value; i++)
{
player = &g_edicts[i];
if (!player->inuse || (player->client == who))
continue;
switch (player->client->resp.ctf_team)
{
case CTF_TEAM1:
team1count++;
break;
case CTF_TEAM2:
team2count++;
}
}
if (team1count < team2count)
who->resp.ctf_team = CTF_TEAM1;
else if (team2count < team1count)
who->resp.ctf_team = CTF_TEAM2;
else if (rand() & 1)
who->resp.ctf_team = CTF_TEAM1;
else
who->resp.ctf_team = CTF_TEAM2;
}
/*
================
SelectCTFSpawnPoint
go to a ctf point, but NOT the two points closest
to other players
================
*/
edict_t *SelectCTFSpawnPoint(edict_t *ent, qboolean ctf_only) //CW
{
edict_t *spot;
edict_t *spot1;
edict_t *spot2;
char *cname;
float range;
float range1;
float range2;
int count = 0;
int selection;
if (ent->client->resp.ctf_state && !((int)(dmflags->value) & DF_CTF_SPAWNS_ONLY) && !ctf_only) //CW
{
if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
return SelectFarthestDeathmatchSpawnPoint();
else
return SelectRandomDeathmatchSpawnPoint();
}
//CW++
if (!ent->client->resp.ctf_state) // due to addition of DF_CTF_SPAWNS_ONLY check above
//CW--
ent->client->resp.ctf_state++;
switch (ent->client->resp.ctf_team)
{
case CTF_TEAM1:
cname = "info_player_team1";
break;
case CTF_TEAM2:
cname = "info_player_team2";
break;
default:
return SelectRandomDeathmatchSpawnPoint();
}
spot = NULL;
range1 = range2 = 99999.0;
spot1 = spot2 = NULL;
while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
{
count++;
range = PlayersRangeFromSpot(spot);
if (range < range1)
{
range1 = range;
spot1 = spot;
}
else if (range < range2)
{
range2 = range;
spot2 = spot;
}
}
if (!count)
return SelectRandomDeathmatchSpawnPoint();
if (count <= 2)
{
spot1 = spot2 = NULL;
}
else
count -= 2;
selection = rand() % count;
spot = NULL;
do
{
spot = G_Find(spot, FOFS(classname), cname);
if ((spot == spot1) || (spot == spot2))
selection++;
} while (selection--);
return spot;
}
/*------------------------------------------------------------------------*/
/*
CTFFragBonuses
Calculate the bonuses for flag defense, flag carrier defense, etc.
Note that bonuses are not cumulative. You get one, they are in importance
order.
*/
void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker)
{
edict_t *ent;
edict_t *flag;
edict_t *carrier = NULL; //CW
gitem_t *flag_item;
gitem_t *enemy_flag_item;
vec3_t v1;
vec3_t v2;
char *c;
int i;
int otherteam;
if (targ->client && attacker->client)
{
if (attacker->client->resp.ghost)
{
if (attacker != targ)
attacker->client->resp.ghost->kills++;
}
if (targ->client->resp.ghost)
targ->client->resp.ghost->deaths++;
}
// no bonus for fragging yourself
if (!targ->client || !attacker->client || (targ == attacker))
return;
otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
if (otherteam < 0)
return; // whoever died isn't on a team
// same team, if the flag at base, check to he has the enemy flag
if (targ->client->resp.ctf_team == CTF_TEAM1)
{
flag_item = flag1_item;
enemy_flag_item = flag2_item;
}
else
{
flag_item = flag2_item;
enemy_flag_item = flag1_item;
}
// did the attacker frag the flag carrier?
if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)])
{
attacker->client->resp.ctf_lastfraggedcarrier = level.time;
attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
gi_cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n", CTF_FRAG_CARRIER_BONUS);
// the target had the flag, clear the hurt carrier
// field on the other team
for (i = 1; i <= (int)maxclients->value; i++)
{
ent = g_edicts + i;
if (ent->inuse && ent->client->resp.ctf_team == otherteam)
ent->client->resp.ctf_lasthurtcarrier = 0;
}
return;
}
if (targ->client->resp.ctf_lasthurtcarrier &&
(level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT) &&
!attacker->client->pers.inventory[ITEM_INDEX(flag_item)])
{
// attacker is on the same team as the flag carrier and
// fragged a guy who hurt our flag carrier
attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
gi_bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n",
attacker->client->pers.netname,
CTFTeamName(attacker->client->resp.ctf_team));
if (attacker->client->resp.ghost)
attacker->client->resp.ghost->carrierdef++;
return;
}
// flag and flag carrier area defense bonuses
// we have to find the flag and carrier entities
// find the flag
switch (attacker->client->resp.ctf_team)
{
case CTF_TEAM1:
c = "item_flag_team1";
break;
case CTF_TEAM2:
c = "item_flag_team2";
break;
default:
return;
}
flag = NULL;
while ((flag = G_Find(flag, FOFS(classname), c)) != NULL)
{
if (!(flag->spawnflags & DROPPED_ITEM))
break;
}
if (!flag)
return; // can't find attacker's flag
// find attacker's team's flag carrier
for (i = 1; i <= (int)maxclients->value; i++)
{
carrier = g_edicts + i;
if (carrier->inuse && carrier->client->pers.inventory[ITEM_INDEX(flag_item)])
break;
carrier = NULL;
}
// ok we have the attackers flag and a pointer to the carrier
// check to see if we are defending the base's flag
VectorSubtract(targ->s.origin, flag->s.origin, v1);
VectorSubtract(attacker->s.origin, flag->s.origin, v2);
if (((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS) || (VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS) ||
loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) && (attacker->client->resp.ctf_team != targ->client->resp.ctf_team))
{
// we defended the base flag
attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
if (flag->solid == SOLID_NOT)
gi_bprintf(PRINT_MEDIUM, "%s defends the %s base.\n", attacker->client->pers.netname, CTFTeamName(attacker->client->resp.ctf_team));
else
gi_bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n", attacker->client->pers.netname, CTFTeamName(attacker->client->resp.ctf_team));
if (attacker->client->resp.ghost)
attacker->client->resp.ghost->basedef++;
return;
}
if (carrier && (carrier != attacker))
{
VectorSubtract(targ->s.origin, carrier->s.origin, v1);
VectorSubtract(attacker->s.origin, carrier->s.origin, v1);
if ((VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS) || (VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS) ||
loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker))
{
attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
gi_bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n", attacker->client->pers.netname, CTFTeamName(attacker->client->resp.ctf_team));
if (attacker->client->resp.ghost)
attacker->client->resp.ghost->carrierdef++;
return;
}
}
}
void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
{
gitem_t *flag_item;
if (!targ->client || !attacker->client)
return;
if (targ->client->resp.ctf_team == CTF_TEAM1)
flag_item = flag2_item;
else
flag_item = flag1_item;
if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] && (targ->client->resp.ctf_team != attacker->client->resp.ctf_team))
attacker->client->resp.ctf_lasthurtcarrier = level.time;
}
/*------------------------------------------------------------------------*/
void CTFResetFlag(int ctf_team)
{
char *c;
edict_t *ent;
switch (ctf_team)
{
case CTF_TEAM1:
c = "item_flag_team1";
break;
case CTF_TEAM2:
c = "item_flag_team2";
break;
default:
return;
}
ent = NULL;
while ((ent = G_Find (ent, FOFS(classname), c)) != NULL)
{
if (ent->spawnflags & DROPPED_ITEM)
G_FreeEdict(ent);
else
{
ent->svflags &= ~SVF_NOCLIENT;
ent->solid = SOLID_TRIGGER;
gi.linkentity(ent);
ent->s.event = EV_ITEM_RESPAWN;
}
}
}
void CTFResetFlags(void)
{
CTFResetFlag(CTF_TEAM1);
CTFResetFlag(CTF_TEAM2);
}
qboolean CTFPickup_Flag(edict_t *ent, edict_t *other)
{
edict_t *player;
gitem_t *flag_item;
gitem_t *enemy_flag_item;
int ctf_team;
int i;
// Figure out what team this flag is.
if (strcmp(ent->classname, "item_flag_team1") == 0)
ctf_team = CTF_TEAM1;
else if (strcmp(ent->classname, "item_flag_team2") == 0)
ctf_team = CTF_TEAM2;
else
{
gi_cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
return false;
}
// Same team; if the flag at base, check to see if he has the enemy flag.
if (ctf_team == CTF_TEAM1)
{
flag_item = flag1_item;
enemy_flag_item = flag2_item;
}
else
{
flag_item = flag2_item;
enemy_flag_item = flag1_item;
}
if (ctf_team == other->client->resp.ctf_team)
{
if (!(ent->spawnflags & DROPPED_ITEM))
{
// The flag is at home base. If the player has the enemy flag, he's just won!
if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)])
{
gi_bprintf(PRINT_HIGH, "%s captured the %s flag!\n", other->client->pers.netname, CTFOtherTeamName(ctf_team));
other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0;
teamgame.last_flag_capture = level.time;
teamgame.last_capture_team = ctf_team;
if (ctf_team == CTF_TEAM1)
teamgame.team1++;
else
teamgame.team2++;
gi.sound(ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
// Other gets another 10 frag bonus.
other->client->resp.score += CTF_CAPTURE_BONUS;
if (other->client->resp.ghost)
other->client->resp.ghost->caps++;
// Ok, let's do the player loop, hand out the bonuses.
for (i = 1; i <= (int)maxclients->value; i++)
{
player = &g_edicts[i];
if (!player->inuse)
continue;
if (player->client->resp.ctf_team != other->client->resp.ctf_team)
player->client->resp.ctf_lasthurtcarrier = -5;
else if (player->client->resp.ctf_team == other->client->resp.ctf_team)
{
if (player != other)
player->client->resp.score += CTF_TEAM_BONUS;
// Award extra points for capture assists.
if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time)
{
gi_bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname);
player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
}
if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time)
{
gi_bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname);
player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
}
}
}
CTFResetFlags();
return false;
}
return false; // its at home base already
}
// Hey, it's not home. Return it by teleporting it back.
gi_bprintf(PRINT_HIGH, "%s returned the %s flag!\n", other->client->pers.netname, CTFTeamName(ctf_team));
other->client->resp.score += CTF_RECOVERY_BONUS;
other->client->resp.ctf_lastreturnedflag = level.time;
gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
//CTFResetFlag will remove this entity! We must return false.
CTFResetFlag(ctf_team);
return false;
}
// Hey, its not our flag, so pick it up.
gi_bprintf(PRINT_HIGH, "%s got the %s flag!\n", other->client->pers.netname, CTFTeamName(ctf_team));
other->client->resp.score += CTF_FLAG_BONUS;
other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1;
other->client->resp.ctf_flagsince = level.time;
//CW++
// Player loses respawn invulnerability upon touching the enemy flag.
if (level.time - other->client->respawn_time < sv_respawn_invuln_time->value)
{
if (other->client->invincible_framenum - level.framenum < 10.0 * sv_respawn_invuln_time->value)
other->client->invincible_framenum = 0;
}
//CW--
// If it's not a dropped flag, we just make is disappear.
// If it's dropped, it will be removed by the pickup caller.
if (!(ent->spawnflags & DROPPED_ITEM))
{
ent->flags |= FL_RESPAWN;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
}
return true;
}
static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
// Owner (who dropped us) can't touch us for two secs.
if ((other == ent->owner) && (ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT - 2))
return;
Touch_Item(ent, other, plane, surf);
}
static void CTFDropFlagThink(edict_t *ent)
{
// Auto return the flag. Reset flag will remove ourselves.
if (strcmp(ent->classname, "item_flag_team1") == 0)
{
CTFResetFlag(CTF_TEAM1);
gi_bprintf(PRINT_HIGH, "The %s flag has returned!\n", CTFTeamName(CTF_TEAM1));
}
else if (strcmp(ent->classname, "item_flag_team2") == 0)
{
CTFResetFlag(CTF_TEAM2);
gi_bprintf(PRINT_HIGH, "The %s flag has returned!\n",CTFTeamName(CTF_TEAM2));
}
}
// Called from PlayerDie, to drop the flag from a dying player
void CTFDeadDropFlag(edict_t *self)
{
edict_t *dropped = NULL;
if (self->client->pers.inventory[ITEM_INDEX(flag1_item)])
{
dropped = Drop_Item(self, flag1_item);
self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0;
gi_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", self->client->pers.netname, CTFTeamName(CTF_TEAM1));
}
else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)])
{
dropped = Drop_Item(self, flag2_item);
self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0;
gi_bprintf(PRINT_HIGH, "%s lost the %s flag!\n", self->client->pers.netname, CTFTeamName(CTF_TEAM2));
}
if (dropped)
{
dropped->think = CTFDropFlagThink;
dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
dropped->touch = CTFDropFlagTouch;
}
}
void CTFDrop_Flag(edict_t *ent, gitem_t *item) //CW
{
if (rand() & 1)
gi_cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n");
else
gi_cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n");
return; //CW
}
static void CTFFlagThink(edict_t *ent)
{
if (ent->solid != SOLID_NOT)
ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
ent->nextthink = level.time + FRAMETIME;
}
void CTFFlagSetup(edict_t *ent)
{
trace_t tr;
vec3_t dest;
float *v;
v = tv(-15.0, -15.0, -15.0);
VectorCopy(v, ent->mins);
v = tv(15.0, 15.0, 15.0);
VectorCopy(v, ent->maxs);
if (ent->model)
gi.setmodel(ent, ent->model);
else
gi.setmodel(ent, ent->item->world_model);
ent->solid = SOLID_TRIGGER;
ent->movetype = MOVETYPE_TOSS;
ent->touch = Touch_Item;
v = tv(0.0, 0.0, -128.0);
VectorAdd(ent->s.origin, v, dest);
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
if (tr.startsolid)
{
gi.dprintf("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
G_FreeEdict(ent);
return;
}
VectorCopy(tr.endpos, ent->s.origin);
gi.linkentity(ent);
ent->think = CTFFlagThink;
ent->nextthink = level.time + FRAMETIME;
}
void CTFEffects(edict_t *player)
{
player->s.effects &= ~(EF_FLAG1 | EF_FLAG2);
if (player->health > 0)
{
if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
player->s.effects |= EF_FLAG1;
if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
player->s.effects |= EF_FLAG2;
}
if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
player->s.modelindex3 = gi.modelindex("players/male/flag1.md2");
else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
player->s.modelindex3 = gi.modelindex("players/male/flag2.md2");
else
player->s.modelindex3 = 0;
}
// called when we enter the intermission
void CTFCalcScores(void)
{
int i;
teamgame.total1 = teamgame.total2 = 0;
for (i = 0; i < (int)maxclients->value; i++)
{
if (!g_edicts[i+1].inuse)
continue;
if (game.clients[i].resp.ctf_team == CTF_TEAM1)
teamgame.total1 += game.clients[i].resp.score;
else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
teamgame.total2 += game.clients[i].resp.score;
}
}
void CTFID_f(edict_t *ent)
{
if (ent->client->resp.id_state)
{
gi_cprintf(ent, PRINT_HIGH, "Disabling player identification display.\n");
ent->client->resp.id_state = false;
}
else
{
gi_cprintf(ent, PRINT_HIGH, "Activating player identification display.\n");
ent->client->resp.id_state = true;
}
}
static void CTFSetIDView(edict_t *ent)
{
edict_t *who;
edict_t *best;
vec3_t forward;
vec3_t dir;
trace_t tr;
float bd = 0;
float d;
int i;
// Only check every few frames.
if (level.time - ent->client->resp.lastidtime < CLIENT_ID_CHECKTIME) //CW
return;
ent->client->resp.lastidtime = level.time;
ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
VectorScale(forward, 1024.0, forward);
VectorAdd(ent->s.origin, forward, forward);
tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
if ((tr.fraction < 1.0) && tr.ent && tr.ent->client)
{
ent->client->ps.stats[STAT_CTF_ID_VIEW] = CS_GENERAL + (tr.ent - g_edicts - 1);
if (tr.ent->client->resp.ctf_team == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
else if (tr.ent->client->resp.ctf_team == CTF_TEAM2)
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
return;
}
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
best = NULL;
for (i = 1; i <= (int)maxclients->value; i++)
{
who = g_edicts + i;
if (!who->inuse || (who->solid == SOLID_NOT))
continue;
VectorSubtract(who->s.origin, ent->s.origin, dir);
VectorNormalize(dir);
d = DotProduct(forward, dir);
if ((d > bd) && loc_CanSee(ent, who))
{
bd = d;
best = who;
}
}
if (bd > 0.90)
{
ent->client->ps.stats[STAT_CTF_ID_VIEW] = CS_GENERAL + (best - g_edicts - 1);
if (best->client->resp.ctf_team == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
else if (best->client->resp.ctf_team == CTF_TEAM2)
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
}
}
void SetCTFStats(edict_t *ent)
{
gitem_t *tech;
edict_t *e;
int i;
int p1;
int p2;
ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH;
if (teamgame.warnactive)
ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO;
else
ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0;
// ghosting
if (ent->client->resp.ghost)
{
ent->client->resp.ghost->score = ent->client->resp.score;
// strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname);
Com_strcpy(ent->client->resp.ghost->netname, sizeof(ent->client->resp.ghost->netname), ent->client->pers.netname);
ent->client->resp.ghost->number = ent->s.number;
}
// logo headers for the frag display
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1;
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2;
// if during intermission, we must blink the team header of the winning team
if (level.intermissiontime && (level.framenum & 8)) // blink 1/8th second
{
//CW++
// Victory for Assault games based is solely on whether or not the attacking team achieved their objective(s).
if (sv_gametype->value == G_ASLT)
{
if (asltgame.victory) // attackers were successful
{
if (asltgame.t_attack == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
else
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
}
else // timelimit reached before attackers completed their mission
{
if (asltgame.t_attack == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
else
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
}
}
else
{
//CW--
// note that teamgame.total[12] is set when we go to intermission
if (teamgame.team1 > teamgame.team2)
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
else if (teamgame.team2 > teamgame.team1)
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
else if (teamgame.total1 > teamgame.total2) // frag tie breaker
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
else if (teamgame.total2 > teamgame.total1)
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
else // tie game!
{
ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
}
}
}
// tech icon
i = 0;
ent->client->ps.stats[STAT_CTF_TECH] = 0;
while (tnames[i])
{
if ((tech = FindItemByClassname(tnames[i])) != NULL && ent->client->pers.inventory[ITEM_INDEX(tech)])
{
ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon);
break;
}
i++;
}
// figure out what icon to display for team logos
// three states:
// flag at base
// flag taken
// flag dropped
p1 = imageindex_i_ctf1;
e = G_Find(NULL, FOFS(classname), "item_flag_team1");
if (e != NULL)
{
if (e->solid == SOLID_NOT)
{
int i;
// not at base; check if on player
p1 = imageindex_i_ctf1d; // default to dropped
for (i = 1; i <= (int)maxclients->value; i++)
if (g_edicts[i].inuse && g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)])
{
// enemy has it
p1 = imageindex_i_ctf1t;
break;
}
}
else if (e->spawnflags & DROPPED_ITEM)
p1 = imageindex_i_ctf1d; // must be dropped
}
p2 = imageindex_i_ctf2;
e = G_Find(NULL, FOFS(classname), "item_flag_team2");
if (e != NULL)
{
if (e->solid == SOLID_NOT)
{
int i;
// not at base; check if on player
p2 = imageindex_i_ctf2d; // default to dropped
for (i = 1; i <= (int)maxclients->value; i++)
{
if (g_edicts[i].inuse && g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)])
{
// enemy has it
p2 = imageindex_i_ctf2t;
break;
}
}
}
else if (e->spawnflags & DROPPED_ITEM)
p2 = imageindex_i_ctf2d; // must be dropped
}
ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
if (teamgame.last_flag_capture && (level.time - teamgame.last_flag_capture < 5.0))
{
if (teamgame.last_capture_team == CTF_TEAM1)
{
if (level.framenum & 8)
ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
else
ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
}
else
{
if (level.framenum & 8)
ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
else
ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
}
}
//CW++
if (sv_gametype->value == G_CTF)
{
//CW---
ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = teamgame.team1;
ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = teamgame.team2;
//CW++
}
else if ((sv_gametype->value == G_TDM) || (sv_gametype->value == G_ASLT)) //display frags instead of caps
{
ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = teamgame.frags1;
ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = teamgame.frags2;
}
//CW--
ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
//CW++
if (sv_gametype->value == G_CTF)
{
//CW--
if ((ent->client->resp.ctf_team == CTF_TEAM1) && ent->client->pers.inventory[ITEM_INDEX(flag2_item)] && (level.framenum & 8))
ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2;
else if ((ent->client->resp.ctf_team == CTF_TEAM2) && ent->client->pers.inventory[ITEM_INDEX(flag1_item)] && (level.framenum & 8))
ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1;
}
//CW++
// Indicate the attacking team.
ent->client->ps.stats[STAT_ASLT_ATTACK2] = 0;
if (sv_gametype->value == G_ASLT)
{
if (asltgame.t_attack == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_aslta;
else
ent->client->ps.stats[STAT_ASLT_ATTACK2] = imageindex_i_aslta;
}
//CW--
ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
if (ent->client->resp.ctf_team == CTF_TEAM1)
ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj;
else if (ent->client->resp.ctf_team == CTF_TEAM2)
ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj;
//CW++
if ((int)sv_gametype->value > G_FFA)
{
//CW--
if (ent->client->resp.id_state)
CTFSetIDView(ent);
else
{
ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
}
}
}
/*------------------------------------------------------------------------*/
/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
potential team1 spawning position for ctf games
*/
void SP_info_player_team1(edict_t *self)
{
//CW++
if (sv_gametype->value != G_CTF)
{
G_FreeEdict(self);
return;
}
self->svflags |= SVF_NOCLIENT;
//CW--
}
/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
potential team2 spawning position for ctf games
*/
void SP_info_player_team2(edict_t *self)
{
//CW++
if (sv_gametype->value != G_CTF)
{
G_FreeEdict(self);
return;
}
self->svflags |= SVF_NOCLIENT;
//CW--
}
/*
------------------------------------------------------------------------
GRAPPLE
------------------------------------------------------------------------
*/
// ent is player
void CTFPlayerResetGrapple(edict_t *ent)
{
if (ent->client && ent->client->ctf_grapple)
CTFResetGrapple(ent->client->ctf_grapple);
}
// self is grapple, not player
void CTFResetGrapple(edict_t *self)
{
//CW++
// Sanity checks.
if (!self->owner)
return;
if (!self->owner->client)
return;
//CW--
if (self->owner->client->ctf_grapple)
{
gclient_t *cl;
float volume = 1.0;
if (self->owner->client->silencer_shots)
volume = 0.2;
gi.sound(self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0);
cl = self->owner->client;
cl->ctf_grapple = NULL;
cl->ctf_grapplereleasetime = level.time;
cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
//CW++
if ((int)sv_hook_offhand->value)
cl->hookstate = WEAPON_READY;
//CW--
G_FreeEdict(self);
}
}
void CTFGrappleTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
float volume = 1.0;
if (other == self->owner)
return;
if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
return;
if (surf && (surf->flags & SURF_SKY))
{
CTFResetGrapple(self);
return;
}
//CW++
if (other->wep_proj)
return;
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
//CW--
VectorCopy(vec3_origin, self->velocity);
if (other->takedamage)
{
T_Damage(other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE);
CTFResetGrapple(self);
return;
}
self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
self->enemy = other;
self->solid = SOLID_NOT;
if (self->owner->client->silencer_shots)
volume = 0.2;
gi.sound(self->owner, CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); //CW
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_SPARKS);
gi.WritePosition(self->s.origin);
if (!plane)
gi.WriteDir(vec3_origin);
else
gi.WriteDir(plane->normal);
gi.multicast(self->s.origin, MULTICAST_PVS);
//CW++
if (plane)
VectorCopy(plane->normal, self->move_angles); // used in CTFGrapplePull()
gi.linkentity(self);
//CW--
}
// draw beam between grapple and self
void CTFGrappleDrawCable(edict_t *self)
{
vec3_t offset;
vec3_t start;
vec3_t end;
vec3_t f;
vec3_t r;
vec3_t dir;
float distance;
AngleVectors(self->owner->client->v_angle, f, r, NULL);
//CW++
if ((int)sv_hook_offhand->value)
VectorSet(offset, 0.0, 0.0, self->owner->viewheight-8.0);
else
//CW--
VectorSet(offset, 16.0, 16.0, self->owner->viewheight-8.0);
P_ProjectSource(self->owner->client, self->owner->s.origin, offset, f, r, start);
VectorSubtract(start, self->owner->s.origin, offset);
VectorSubtract(start, self->s.origin, dir);
// don't draw cable if too close
distance = VectorLength(dir);
if (distance < 64.0)
return;
VectorCopy(self->s.origin, end); //CW
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_GRAPPLE_CABLE);
gi.WriteShort(self->owner - g_edicts);
gi.WritePosition(self->owner->s.origin);
gi.WritePosition(end);
gi.WritePosition(offset);
gi.multicast(self->s.origin, MULTICAST_PVS);
}
void SV_AddGravity(edict_t *ent);
// pull the player toward the grapple
void CTFGrapplePull(edict_t *self)
{
vec3_t hookdir;
vec3_t v;
float vlen;
//CW++
float speed;
//CW--
if ((self->owner->client->pers.weapon->weapmodel == WEAP_GRAPPLE) && !self->owner->client->newweapon &&
(self->owner->client->weaponstate != WEAPON_FIRING) && (self->owner->client->weaponstate != WEAPON_ACTIVATING)) //CW
{
CTFResetGrapple(self);
return;
}
//CW++
if ((int)sv_hook_offhand->value && !self->owner->client->hook_on && (self->owner->client->hookstate != WEAPON_FIRING))
{
CTFResetGrapple(self);
return;
}
//CW--
if (self->enemy)
{
if (self->enemy->solid == SOLID_NOT)
{
CTFResetGrapple(self);
return;
}
if (self->enemy->solid == SOLID_BBOX)
{
VectorScale(self->enemy->size, 0.5, v);
VectorAdd(v, self->enemy->s.origin, v);
VectorAdd(v, self->enemy->mins, self->s.origin);
gi.linkentity(self);
}
else
VectorCopy(self->enemy->velocity, self->velocity);
if (self->enemy->takedamage && !CheckTeamDamage(self->enemy, self->owner))
{
float volume = 1.0;
if (self->owner->client->silencer_shots)
volume = 0.2;
T_Damage(self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0);
}
if (self->enemy->deadflag) // he died
{
CTFResetGrapple(self);
return;
}
}
CTFGrappleDrawCable(self);
if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
{
// pull player toward grapple
// this causes icky stuff with prediction, we need to extend
// the prediction layer to include two new fields in the player
// move stuff: a point and a velocity. The client should add
// that velocity in the direction of the point
vec3_t forward;
vec3_t up;
AngleVectors(self->owner->client->v_angle, forward, NULL, up);
VectorCopy(self->owner->s.origin, v);
v[2] += self->owner->viewheight;
VectorSubtract(self->s.origin, v, hookdir);
vlen = VectorLength(hookdir);
if ((self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) && (vlen < 64.0))
{
float volume = 1.0;
if (self->owner->client->silencer_shots)
volume = 0.2;
self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
gi.sound(self->owner, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); //CW
self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
}
//CW++
speed = sv_hook_pull_speed->value;
if (self->owner->client->held_by_agm)
speed *= sv_hook_pull_agm->value;
//CW--
//CW++
// When hanging on the grapple next to a surface, make the pulling direction be the
// negative of the normal vector of the plane. This prevents unwanted oscillations
// when the grapple has hit the surface at a shallow angle.
if (vlen < 16.0)
VectorScale(self->move_angles, -1.0, hookdir);
else
//CW--
VectorNormalize(hookdir);
VectorScale(hookdir, speed, hookdir); //CW
VectorCopy(hookdir, self->owner->velocity);
SV_AddGravity(self->owner);
}
}
void CTFFireGrapple(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
{
edict_t *grapple;
trace_t tr;
VectorNormalize(dir);
grapple = G_Spawn();
grapple->solid = SOLID_BBOX;
grapple->movetype = MOVETYPE_FLYMISSILE;
grapple->clipmask = MASK_SHOT;
grapple->s.effects |= effect;
grapple->owner = self;
grapple->touch = CTFGrappleTouch;
grapple->dmg = damage;
self->client->ctf_grapple = grapple;
self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
grapple->s.modelindex = gi.modelindex("models/weapons/grapple/hook/tris.md2");
VectorCopy(start, grapple->s.origin);
VectorCopy(start, grapple->s.old_origin);
vectoangles(dir, grapple->s.angles);
VectorScale(dir, speed, grapple->velocity);
//CW++
grapple->svflags = SVF_PROJECTILE;
//CW--
gi.linkentity(grapple);
tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT);
if (tr.fraction < 1.0)
{
VectorMA(grapple->s.origin, -10.0, dir, grapple->s.origin);
grapple->touch(grapple, tr.ent, NULL, NULL);
}
}
void CTFGrappleFire(edict_t *ent, vec3_t g_offset, int damage, int effect)
{
vec3_t forward;
vec3_t right;
vec3_t start;
vec3_t offset;
float volume = 1.0;
if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
return; // it's already out
AngleVectors(ent->client->v_angle, forward, right, NULL);
//CW++
if ((int)sv_hook_offhand->value)
VectorSet(offset, 0.0, 0.0, ent->viewheight-6.0);
else
//CW--
VectorSet(offset, 24.0, 8.0, ent->viewheight-6.0); //CW
VectorAdd(offset, g_offset, offset);
P_ProjectSource(ent->client, ent->s.origin, offset, forward, right, start);
VectorScale(forward, -2.0, ent->client->kick_origin);
ent->client->kick_angles[0] = -1.0;
if (ent->client->silencer_shots)
volume = 0.2;
//CW++
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
CTFFireGrapple(ent, start, forward, damage, (int)sv_hook_speed->value, effect);
PlayerNoise(ent, start, PNOISE_WEAPON);
//CW--
}
void CTFWeapon_Grapple_Fire(edict_t *ent)
{
int damage;
//CW++
if (ent->deadflag || ent->s.modelindex != (MAX_MODELS-1)) // was 255
return;
damage = (int)sv_hook_damage->value;
if (ent->client->quad_framenum > level.framenum)
damage *= 4;
//CW--
CTFGrappleFire(ent, vec3_origin, damage, 0); //CW
ent->client->ps.gunframe++;
}
//CW++
void CTFWeapon_Grapple_OffHand(edict_t *self)
{
int damage;
//CW++
if (self->deadflag || (self->s.modelindex != (MAX_MODELS-1))) // was 255
return;
//CW--
if (self->health < 1) //CW
return;
if (self->client->hook_on)
{
if (self->client->frozen_framenum > level.framenum)
{
if (self->client->ctf_grapple)
CTFResetGrapple(self->client->ctf_grapple);
if (self->client->hookstate == WEAPON_FIRING)
self->client->hookstate = WEAPON_READY;
self->client->hook_on = false;
return;
}
if (self->client->hookstate != WEAPON_FIRING)
{
damage = (int)sv_hook_damage->value;
if (self->client->quad_framenum > level.framenum)
damage *= 4;
CTFGrappleFire(self, vec3_origin, damage, 0);
}
if (self->client->ctf_grapple)
self->client->hookstate = WEAPON_FIRING;
return;
}
if (self->client->ctf_grapple) // hook_on will be false at this point
{
CTFResetGrapple(self->client->ctf_grapple);
if (self->client->hookstate == WEAPON_FIRING)
self->client->hookstate = WEAPON_READY;
}
}
//CW--
void CTFWeapon_Grapple(edict_t *ent)
{
static int pause_frames[] = {10, 18, 27, 0};
static int fire_frames[] = {6, 0};
int prevstate;
// if the the attack button is still down, stay in the firing frame
if ((ent->client->buttons & BUTTON_ATTACK) && (ent->client->weaponstate == WEAPON_FIRING) && ent->client->ctf_grapple)
ent->client->ps.gunframe = 9;
if (!(ent->client->buttons & BUTTON_ATTACK) && ent->client->ctf_grapple)
{
CTFResetGrapple(ent->client->ctf_grapple);
if (ent->client->weaponstate == WEAPON_FIRING)
ent->client->weaponstate = WEAPON_READY;
}
if (ent->client->newweapon && (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) && (ent->client->weaponstate == WEAPON_FIRING))
{
// he wants to change weapons while grappled
ent->client->weaponstate = WEAPON_DROPPING;
ent->client->ps.gunframe = 32;
}
prevstate = ent->client->weaponstate;
Weapon_Generic(ent, 5, 9, 31, 36, pause_frames, fire_frames, CTFWeapon_Grapple_Fire);
// if we just switched back to grapple, immediately go to fire frame
if ((prevstate == WEAPON_ACTIVATING) && (ent->client->weaponstate == WEAPON_READY) && (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY))
{
if (!(ent->client->buttons & BUTTON_ATTACK))
ent->client->ps.gunframe = 9;
else
ent->client->ps.gunframe = 5;
ent->client->weaponstate = WEAPON_FIRING;
}
}
void CTFTeam_f(edict_t *ent)
{
char *t;
char *s;
int desired_team;
//CW++
edict_t *index;
edict_t *check;
qboolean finished = false;
if (sv_gametype->value == G_FFA)
{
gi_cprintf(ent, PRINT_HIGH, "You don't have a team in FFA games!\n");
return;
}
//CW--
t = gi.args();
if (!*t)
{
gi_cprintf(ent, PRINT_HIGH, "You are on the %s team.\n", CTFTeamName(ent->client->resp.ctf_team));
return;
}
if (teamgame.match > MATCH_SETUP)
{
gi_cprintf(ent, PRINT_HIGH, "Can't change teams during a match.\n");
return;
}
if (Q_stricmp(t, "red") == 0)
desired_team = CTF_TEAM1;
else if (Q_stricmp(t, "blue") == 0)
desired_team = CTF_TEAM2;
//CW++
else if (!Q_stricmp(t, sv_team1_name->string))
desired_team = CTF_TEAM1;
else if (!Q_stricmp(t, sv_team2_name->string))
desired_team = CTF_TEAM2;
//CW--
else
{
gi_cprintf(ent, PRINT_HIGH, "Unknown team.\n", t);
return;
}
if (ent->client->resp.ctf_team == desired_team)
{
gi_cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n", CTFTeamName(ent->client->resp.ctf_team));
return;
}
//CW++
ent->s.effects = 0;
ent->s.renderfx = 0;
// If the player is using a spycam, bring them out of it first.
if (ent->client->spycam)
camera_off(ent);
// Search through the player's linked list of Trap and C4 entities (if any), and pop them.
if (ent->next_node)
{
index = ent->next_node;
while (index && !finished)
{
check = index;
if (index->next_node)
index = index->next_node;
else
finished = true;
if (check->die == C4_DieFromDamage)
C4_Die(check);
else if (check->die == Trap_DieFromDamage)
Trap_Die(check);
else
gi.dprintf("BUG: Invalid next_node pointer in CTFTeam_f().\nPlease contact musashi@planetquake.com\n");
}
}
//CW--
ent->svflags = 0;
ent->flags &= ~FL_GODMODE;
ent->client->resp.ctf_team = desired_team;
ent->client->resp.ctf_state = 0;
s = Info_ValueForKey(ent->client->pers.userinfo, "skin");
CTFAssignSkin(ent, s);
if (ent->solid == SOLID_NOT) // observer
{
//CW++
ent->client->spectator = false;
//CW--
PutClientInServer(ent);
// Add a teleportation effect, and hold the player in place briefly.
ent->s.event = EV_PLAYER_TELEPORT;
ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
ent->client->ps.pmove.pm_time = 14;
gi_bprintf(PRINT_HIGH, "%s joined the %s team.\n", ent->client->pers.netname, CTFTeamName(desired_team));
return;
}
ent->health = 0;
//CW++
if ((sv_gametype->value == G_TDM) || (sv_gametype->value == G_ASLT))
ent->client->mod_changeteam = true;
//CW--
player_die(ent, ent, world, 100000, vec3_origin); //CW
// Don't even bother waiting for death frames.
ent->deadflag = DEAD_DEAD;
Respawn(ent);
ent->client->resp.score = 0;
gi_bprintf(PRINT_HIGH, "%s changed to the %s team.\n", ent->client->pers.netname, CTFTeamName(desired_team));
}
/*
==================
CTFScoreboardMessage
==================
*/
void CTFScoreboardMessage(edict_t *ent, edict_t *killer)
{
gclient_t *cl;
edict_t *cl_ent;
char entry[1024];
char string[1400];
int len;
int sorted[2][MAX_CLIENTS];
int sortedscores[2][MAX_CLIENTS];
int score;
int total[2];
int totalscore[2];
int last[2];
int team;
int maxsize = 1000;
int i, j, k, n;
size_t entryLen, stringLen;
// sort the clients by team and score
total[0] = total[1] = 0;
last[0] = last[1] = 0;
totalscore[0] = totalscore[1] = 0;
for (i = 0; i < game.maxclients; i++)
{
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse)
continue;
if (game.clients[i].resp.ctf_team == CTF_TEAM1)
team = 0;
else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
team = 1;
else
continue; // unknown team?
score = game.clients[i].resp.score;
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]++;
}
// print level name and exit rules
// add the clients in sorted order
*string = 0;
len = 0;
// team one
Com_sprintf(string, sizeof(string),
"if 24 xv 8 yv 8 pic 24 endif "
"xv 40 yv 28 string \"%4d/%-3d\" "
"xv 98 yv 12 num 2 18 "
"if 25 xv 168 yv 8 pic 25 endif "
"xv 200 yv 28 string \"%4d/%-3d\" "
"xv 256 yv 12 num 2 20 ",
totalscore[0], total[0],
totalscore[1], total[1]);
len = (int)strlen(string);
for (i = 0; i < 16 ; i++)
{
if ((i >= total[0]) && (i >= total[1]))
break; // we're done
*entry = 0;
// left side
if (i < total[0])
{
cl = &game.clients[sorted[0][i]];
cl_ent = g_edicts + 1 + sorted[0][i];
entryLen = strlen(entry);
// sprintf(entry + strlen(entry),
Com_sprintf(entry + entryLen,
sizeof(entry) - entryLen,
"ctf 0 %d %d %d %d ",
42 + (i * 8),
sorted[0][i],
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
// sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ", 42 + (i * 8));
entryLen = strlen(entry);
Com_sprintf(entry + entryLen, sizeof(entry) - entryLen, "xv 56 yv %d picn sbfctf2 ", 42 + (i * 8));
}
if (maxsize - len > strlen(entry))
{
// strcat(string, entry);
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
last[0] = i;
}
}
*entry = 0; //CW++
// right side
if (i < total[1])
{
cl = &game.clients[sorted[1][i]];
cl_ent = g_edicts + 1 + sorted[1][i];
entryLen = strlen(entry);
// sprintf(entry + strlen(entry),
Com_sprintf(entry + entryLen,
sizeof(entry) - entryLen,
"ctf 160 %d %d %d %d ",
42 + (i * 8),
sorted[1][i],
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
// sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ", 42 + i * 8);
entryLen = strlen(entry);
Com_sprintf(entry + entryLen, sizeof(entry) - entryLen, "xv 216 yv %d picn sbfctf1 ", 42 + i * 8);
}
if (maxsize - len > strlen(entry))
{
// strcat(string, entry);
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
last[1] = i;
}
}
}
// put in spectators if we have enough room
if (last[0] > last[1])
j = last[0];
else
j = last[1];
j = ((j + 2) * 8) + 42;
k = n = 0;
if (maxsize - len > 50)
{
for (i = 0; i < (int)maxclients->value; i++)
{
cl_ent = g_edicts + 1 + i;
cl = &game.clients[i];
if (!cl_ent->inuse || (cl_ent->solid != SOLID_NOT) || (cl_ent->client->resp.ctf_team != CTF_NOTEAM))
continue;
if (!k)
{
k = 1;
Com_sprintf(entry, sizeof(entry), "xv 0 yv %d string2 \"Spectators\" ", j);
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
j += 8;
}
entryLen = strlen(entry);
// sprintf(entry + strlen(entry),
Com_sprintf(entry + entryLen,
sizeof(entry) - entryLen,
"ctf %d %d %d %d %d ",
(n & 1) ? 160 : 0, // x
j, // y
i, // playernum
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (maxsize - len > strlen(entry))
{
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
}
if (n & 1)
j += 8;
n++;
}
}
if (total[0] - last[0] > 1) { // couldn't fit everyone
// sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", 42 + (last[0]+1)*8, total[0] - last[0] - 1);
stringLen = strlen(string);
Com_sprintf(string + stringLen, sizeof(string) - stringLen, "xv 8 yv %d string \"..and %d more\" ", 42 + (last[0]+1)*8, total[0] - last[0] - 1);
}
if (total[1] - last[1] > 1) { // couldn't fit everyone
// sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", 42 + (last[1]+1)*8, total[1] - last[1] - 1);
stringLen = strlen(string);
Com_sprintf(string + stringLen, sizeof(string) - stringLen, "xv 168 yv %d string \"..and %d more\" ", 42 + (last[1]+1)*8, total[1] - last[1] - 1);
}
gi.WriteByte(svc_layout);
gi.WriteString(string);
}
/*------------------------------------------------------------------------*/
/* TECH */
/*------------------------------------------------------------------------*/
void CTFHasTech(edict_t *who)
{
if (level.time - who->client->ctf_lasttechmsg > 2.0)
{
gi_centerprintf(who, "You already have a TECH powerup.");
who->client->ctf_lasttechmsg = level.time;
}
}
gitem_t *CTFWhat_Tech(edict_t *ent)
{
gitem_t *tech;
int i;
i = 0;
while (tnames[i])
{
if (((tech = FindItemByClassname(tnames[i])) != NULL) && ent->client->pers.inventory[ITEM_INDEX(tech)])
return tech;
i++;
}
return NULL;
}
qboolean CTFPickup_Tech(edict_t *ent, edict_t *other)
{
gitem_t *tech;
int i;
i = 0;
while (tnames[i])
{
if (((tech = FindItemByClassname(tnames[i])) != NULL) && other->client->pers.inventory[ITEM_INDEX(tech)])
{
CTFHasTech(other);
return false; // has this one
}
i++;
}
// client only gets one tech
other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
other->client->ctf_regentime = level.time;
return true;
}
static void SpawnTech(gitem_t *item, edict_t *spot);
edict_t *FindTechSpawn(void) //CW
{
edict_t *spot = NULL;
int i = rand() % 16;
while (i--)
spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
if (!spot)
spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
return spot;
}
static void TechThink(edict_t *tech)
{
edict_t *spot;
if ((spot = FindTechSpawn()) != NULL)
{
SpawnTech(tech->item, spot);
G_FreeEdict(tech);
}
else
{
tech->nextthink = level.time + CTF_TECH_TIMEOUT;
tech->think = TechThink;
}
}
void CTFDrop_Tech(edict_t *ent, gitem_t *item)
{
edict_t *tech;
tech = Drop_Item(ent, item);
tech->nextthink = level.time + CTF_TECH_TIMEOUT;
tech->think = TechThink;
ent->client->pers.inventory[ITEM_INDEX(item)] = 0;
}
void CTFDeadDropTech(edict_t *ent)
{
gitem_t *tech;
edict_t *dropped;
int i;
i = 0;
while (tnames[i])
{
if (((tech = FindItemByClassname(tnames[i])) != NULL) && ent->client->pers.inventory[ITEM_INDEX(tech)])
{
dropped = Drop_Item(ent, tech);
dropped->velocity[0] = (float)(rand() % 600) - 300.0;
dropped->velocity[1] = (float)(rand() % 600) - 300.0;
dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
dropped->think = TechThink;
dropped->owner = NULL;
ent->client->pers.inventory[ITEM_INDEX(tech)] = 0;
}
i++;
}
}
static void SpawnTech(gitem_t *item, edict_t *spot)
{
edict_t *ent;
vec3_t forward;
vec3_t right;
vec3_t angles;
ent = G_Spawn();
ent->classname = item->classname;
ent->item = item;
ent->spawnflags = DROPPED_ITEM;
ent->s.effects = item->world_model_flags;
ent->s.renderfx = RF_GLOW;
VectorSet(ent->mins, -15.0, -15.0, -15.0);
VectorSet(ent->maxs, 15.0, 15.0, 15.0);
gi.setmodel(ent, ent->item->world_model);
ent->solid = SOLID_TRIGGER;
ent->movetype = MOVETYPE_TOSS;
ent->touch = Touch_Item;
ent->owner = ent;
angles[0] = 0.0;
angles[1] = (float)(rand() % 360);
angles[2] = 0.0;
AngleVectors(angles, forward, right, NULL);
VectorCopy(spot->s.origin, ent->s.origin);
ent->s.origin[2] += 16.0;
VectorScale(forward, 100.0, ent->velocity);
ent->velocity[2] = 300.0;
ent->nextthink = level.time + CTF_TECH_TIMEOUT;
ent->think = TechThink;
gi.linkentity(ent);
}
static void SpawnTechs(edict_t *ent)
{
gitem_t *tech;
edict_t *spot;
int i;
i = 0;
while (tnames[i])
{
if (((tech = FindItemByClassname(tnames[i])) != NULL) && ((spot = FindTechSpawn()) != NULL))
SpawnTech(tech, spot);
i++;
}
if (ent)
G_FreeEdict(ent);
}
// frees the passed edict!
void CTFRespawnTech(edict_t *ent)
{
edict_t *spot;
if ((spot = FindTechSpawn()) != NULL)
SpawnTech(ent->item, spot);
G_FreeEdict(ent);
}
void CTFSetupTechSpawn(void)
{
edict_t *ent;
if (((int)dmflags->value & DF_CTF_NO_TECH))
return;
//CW++
if (sv_gametype->value != G_CTF)
return;
//CW--
ent = G_Spawn();
//CW++
ent->classname = "tech_spawner";
ent->svflags |= SVF_NOCLIENT;
//CW--
ent->nextthink = level.time + 5.0; //CW
ent->think = SpawnTechs;
}
void CTFResetTech(void)
{
edict_t *ent;
int i;
for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++)
{
if (ent->inuse)
{
if (ent->item && (ent->item->flags & IT_TECH))
G_FreeEdict(ent);
}
}
SpawnTechs(NULL);
}
int CTFApplyResistance(edict_t *ent, int dmg)
{
static gitem_t *tech = NULL;
float volume = 1.0;
if (ent->client && ent->client->silencer_shots)
volume = 0.2;
if (!tech)
tech = FindItemByClassname("item_tech1");
if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)])
{
gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
return ((int)(dmg * 0.5));
}
return dmg;
}
int CTFApplyStrength(edict_t *ent, int dmg)
{
static gitem_t *tech = NULL;
if (!tech)
tech = FindItemByClassname("item_tech2");
if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)])
return dmg * 2;
return dmg;
}
qboolean CTFApplyStrengthSound(edict_t *ent)
{
static gitem_t *tech = NULL;
float volume = 1.0;
//CW++
if (ent->client->pers.weapon->weapmodel == WEAP_AGM)
return false;
//CW--
if (!tech)
tech = FindItemByClassname("item_tech2");
if (tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)])
{
if (ent->client->ctf_techsndtime < level.time)
{
ent->client->ctf_techsndtime = level.time + 1.0;
if (ent->client->silencer_shots)
volume = 0.2;
if (ent->client->quad_framenum > level.framenum)
gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
else
gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
}
return true;
}
return false;
}
qboolean CTFApplyHaste(edict_t *ent)
{
static gitem_t *tech = NULL;
if (!tech)
tech = FindItemByClassname("item_tech3");
if (tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)])
return true;
return false;
}
void CTFApplyHasteSound(edict_t *ent)
{
static gitem_t *tech = NULL;
float volume = 1.0;
//CW++
if (!Q_stricmp(ent->client->pers.weapon->pickup_name, "AG Manipulator") && !ent->client->agm_disrupt)
return;
//CW--
if (ent->client && ent->client->silencer_shots)
volume = 0.2;
if (!tech)
tech = FindItemByClassname("item_tech3");
if (tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)] && (ent->client->ctf_techsndtime < level.time))
{
ent->client->ctf_techsndtime = level.time + 1.0;
gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
}
//CW++
else if (ent->client && (ent->client->haste_framenum > level.framenum))
gi.sound(ent, CHAN_VOICE, gi.soundindex("items/haste3.wav"), volume, ATTN_NORM, 0);
//CW--
}
void CTFApplyRegeneration(edict_t *ent)
{
static gitem_t *tech = NULL;
gclient_t *client;
float volume = 1.0;
qboolean noise = false;
int index;
client = ent->client;
if (!client)
return;
if (ent->client->silencer_shots)
volume = 0.2;
if (!tech)
tech = FindItemByClassname("item_tech4");
if (tech && client->pers.inventory[ITEM_INDEX(tech)])
{
if (client->ctf_regentime < level.time)
{
client->ctf_regentime = level.time;
if (ent->health < 150)
{
ent->health += 5;
if (ent->health > 150)
ent->health = 150;
client->ctf_regentime += 0.5;
noise = true;
}
index = ArmorIndex(ent);
if (index && (client->pers.inventory[index] < 150))
{
client->pers.inventory[index] += 5;
if (client->pers.inventory[index] > 150)
client->pers.inventory[index] = 150;
client->ctf_regentime += 0.5;
noise = true;
}
}
if (noise && (ent->client->ctf_techsndtime < level.time))
{
ent->client->ctf_techsndtime = level.time + 1.0;
gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
}
}
}
qboolean CTFHasRegeneration(edict_t *ent)
{
static gitem_t *tech = NULL;
if (!tech)
tech = FindItemByClassname("item_tech4");
if (tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)])
return true;
return false;
}
/*
======================================================================
SAY_TEAM
======================================================================
*/
// This array is in 'importance order', it indicates what items are
// more important when reporting their names.
struct {
char *classname;
int priority;
} loc_names[] =
{
{ "item_flag_team1", 1 },
{ "item_flag_team2", 1 },
{ "item_quad", 2 },
{ "item_invulnerability", 2 },
//CW++
{ "item_siphon", 2 },
{ "item_needle", 2 },
{ "item_haste", 2 },
{ "weapon_shockrifle", 3 },
{ "weapon_gausspistol", 3 },
{ "weapon_railgun", 3 },
{ "weapon_rocketlauncher", 4 },
{ "weapon_agm", 4 },
{ "weapon_esg", 4 },
{ "weapon_flamethrower", 4 },
{ "weapon_mac10", 4 },
{ "weapon_jackhammer", 4 },
{ "ammo_c4", 4 },
{ "ammo_traps", 4 },
//CW--
{ "item_power_screen", 5 },
{ "item_power_shield", 5 },
{ "item_armor_body", 6 },
{ "item_armor_combat", 6 },
{ "item_armor_jacket", 6 },
//CW++
{ "item_teleporter", 7 },
//CW--
{ "item_silencer", 7 },
{ "item_breather", 7 },
{ "item_enviro", 7 },
{ "item_adrenaline", 7 },
{ "item_bandolier", 8 },
{ "item_pack", 8 },
{ NULL, 0 }
};
static void CTFSay_Team_Location (edict_t *who, char *buf, size_t bufSize)
{
edict_t *what = NULL;
edict_t *hot = NULL;
edict_t *flag1;
edict_t *flag2;
gitem_t *item;
vec3_t v;
float hotdist = 999999.0;
float newdist;
int hotindex = 999;
int i;
int nearteam = -1;
qboolean hotsee = false;
qboolean cansee;
char teamname[32]; //CW++
while ((what = loc_findradius(what, who->s.origin, 1024.0)) != NULL)
{
// Find 'what' in 'loc_classnames'.
for (i = 0; loc_names[i].classname; i++)
{
if (!Q_stricmp(what->classname, loc_names[i].classname)) //CW
break;
}
if (!loc_names[i].classname)
continue;
//CW++
// Ignore randomly spawned items.
if (what->rnd_spawn)
continue;
//CW--
// Something we can see gets priority over something we can't.
cansee = loc_CanSee(what, who);
if (cansee && !hotsee)
{
hotsee = true;
hotindex = loc_names[i].priority;
hot = what;
VectorSubtract(what->s.origin, who->s.origin, v);
hotdist = VectorLength(v);
continue;
}
// If we can't see this, but we have something we can see, skip it.
if (hotsee && !cansee)
continue;
if (hotsee && (hotindex < loc_names[i].priority))
continue;
VectorSubtract(what->s.origin, who->s.origin, v);
newdist = VectorLength(v);
if ((newdist < hotdist) || (cansee && loc_names[i].priority < hotindex))
{
hot = what;
hotdist = newdist;
hotindex = i;
hotsee = loc_CanSee(hot, who);
}
}
if (!hot)
{
Com_strcpy(buf, bufSize, "nowhere");
return;
}
// We now have the closest item. See if there's more than one in the map; if so,
// we need to determine what team is closest.
what = NULL;
while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL)
{
if (what == hot)
continue;
// If we are here, there is more than one; find out if 'hot' is closer
// to the red flag or blue flag.
if (((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL) && ((flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL))
{
VectorSubtract(hot->s.origin, flag1->s.origin, v);
hotdist = VectorLength(v);
VectorSubtract(hot->s.origin, flag2->s.origin, v);
newdist = VectorLength(v);
if (hotdist < newdist)
nearteam = CTF_TEAM1;
else if (hotdist > newdist)
nearteam = CTF_TEAM2;
}
break;
}
if ((item = FindItemByClassname(hot->classname)) == NULL)
{
Com_strcpy(buf, bufSize, "nowhere");
return;
}
// Check to see if it's in water.
if (who->waterlevel)
Com_strcpy(buf, bufSize, "in the water ");
else
*buf = 0;
// Determine if it's near or above/below.
VectorSubtract(who->s.origin, hot->s.origin, v);
if ((fabs(v[2]) > fabs(v[0])) && (fabs(v[2]) > fabs(v[1])))
{
if (v[2] > 0)
Com_strcat(buf, bufSize, "above ");
else
Com_strcat(buf, bufSize, "below ");
}
else
Com_strcat(buf, bufSize, "near ");
if (nearteam == CTF_TEAM1)
Com_sprintf(teamname, sizeof(teamname), "the %s ", sv_team1_name->string); //CW
else if (nearteam == CTF_TEAM2)
Com_sprintf(teamname, sizeof(teamname), "the %s ", sv_team2_name->string); //CW
else
Com_sprintf(teamname, sizeof(teamname), "the "); //CW
Com_strcat(buf, bufSize, teamname); //CW++
Com_strcat(buf, bufSize, item->pickup_name);
}
static void CTFSay_Team_Armor (edict_t *who, char *buf, size_t bufSize)
{
gitem_t *item;
int index;
int cells;
int power_armor_type;
size_t bufLen;
*buf = 0;
power_armor_type = PowerArmorType(who);
if (power_armor_type)
{
cells = who->client->pers.inventory[ITEM_INDEX(FindItem("cells"))];
if (cells) {
// sprintf(buf+strlen(buf), "%s with %i cells ", (power_armor_type == POWER_ARMOR_SCREEN)?"Power Screen":"Power Shield", cells);
bufLen = strlen(buf);
Com_sprintf(buf+bufLen, bufSize-bufLen, "%s with %i cells ", (power_armor_type == POWER_ARMOR_SCREEN)?"Power Screen":"Power Shield", cells);
}
}
index = ArmorIndex(who);
if (index)
{
item = GetItemByIndex(index);
if (item)
{
if (*buf)
Com_strcat(buf, bufSize, "and ");
// sprintf(buf+strlen(buf), "%i units of %s", who->client->pers.inventory[index], item->pickup_name);
bufLen = strlen(buf);
Com_sprintf(buf+bufLen, bufSize-bufLen, "%i units of %s", who->client->pers.inventory[index], item->pickup_name);
}
}
if (!*buf)
Com_strcpy(buf, bufSize, "no armor");
}
static void CTFSay_Team_Health (edict_t *who, char *buf, size_t bufSize)
{
if (who->health <= 0)
Com_strcpy(buf, bufSize, "dead");
else
Com_sprintf(buf, bufSize, "%i health", who->health);
}
static void CTFSay_Team_Tech (edict_t *who, char *buf, size_t bufSize)
{
gitem_t *tech;
int i;
// see if the player has a tech powerup
i = 0;
while (tnames[i])
{
if (((tech = FindItemByClassname(tnames[i])) != NULL) && who->client->pers.inventory[ITEM_INDEX(tech)])
{
Com_sprintf(buf, bufSize, "the %s", tech->pickup_name);
return;
}
i++;
}
Com_strcpy(buf, bufSize, "no powerup");
}
static void CTFSay_Team_Weapon (edict_t *who, char *buf, size_t bufSize)
{
if (who->client->pers.weapon)
Com_strcpy(buf, bufSize, who->client->pers.weapon->pickup_name);
else
Com_strcpy(buf, bufSize, "none");
}
static void CTFSay_Team_Sight (edict_t *who, char *buf, size_t bufSize)
{
edict_t *targ;
char s[1024];
char s2[1024];
int i;
int n = 0;
*s = *s2 = 0;
for (i = 1; i <= (int)maxclients->value; i++)
{
targ = g_edicts + i;
if (!targ->inuse || (targ == who) || !loc_CanSee(targ, who))
continue;
if (*s2)
{
if (strlen(s) + strlen(s2) + 3 < sizeof(s))
{
if (n)
Com_strcat(s, sizeof(s), ", ");
Com_strcat(s, sizeof(s), s2);
*s2 = 0;
}
n++;
}
Com_strcpy(s2, sizeof(s2), targ->client->pers.netname);
}
if (*s2)
{
if (strlen(s) + strlen(s2) + 6 < sizeof(s))
{
if (n)
Com_strcat(s, sizeof(s), " and ");
Com_strcat(s, sizeof(s), s2);
}
Com_strcpy(buf, bufSize, s);
}
else
Com_strcpy(buf, bufSize, "no one");
}
void CTFSay_Team (edict_t *who, char *msg)
{
edict_t *cl_ent;
char outmsg[256];
char buf[256];
char *p;
int i;
if (CheckFlood(who))
return;
outmsg[0] = 0;
if (*msg == '\"')
{
msg[strlen(msg) - 1] = 0;
msg++;
}
for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) // was -2
{
if (*msg == '%')
{
switch (*++msg)
{
case 'l' :
case 'L' :
CTFSay_Team_Location(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
case 'a' :
case 'A' :
CTFSay_Team_Armor(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
case 'h' :
case 'H' :
CTFSay_Team_Health(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
case 't' :
case 'T' :
CTFSay_Team_Tech(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
case 'w' :
case 'W' :
CTFSay_Team_Weapon(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
case 'n' :
case 'N' :
CTFSay_Team_Sight(who, buf, sizeof(buf));
if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 1) // was -2
{
Com_strcpy(p, sizeof(outmsg) - (p - outmsg) - 1, buf);
// strcpy(p, buf);
p += strlen(buf);
}
break;
default :
*p++ = *msg;
}
}
else
*p++ = *msg;
}
*p = 0;
for (i = 0; i < (int)maxclients->value; i++)
{
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse)
continue;
if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team)
gi_cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", who->client->pers.netname, outmsg);
}
//CW++
gi.dprintf("(%s -> team): %s\n", who->client->pers.netname, outmsg);
//CW--
}
/*-----------------------------------------------------------------------*/
/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
The origin is the bottom of the banner.
The banner is 248 tall.
*/
static void misc_ctf_banner_think (edict_t *ent)
{
ent->s.frame = (ent->s.frame + 1) % 16;
ent->nextthink = level.time + FRAMETIME;
}
void SP_misc_ctf_banner(edict_t *ent)
{
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_NOT;
ent->s.modelindex = gi.modelindex("models/ctf/banner/tris.md2");
if (ent->spawnflags & 1) // team2
ent->s.skinnum = 1;
ent->s.frame = rand() % 16;
gi.linkentity(ent);
ent->think = misc_ctf_banner_think;
ent->nextthink = level.time + FRAMETIME;
}
/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
The origin is the bottom of the banner.
The banner is 124 tall.
*/
void SP_misc_ctf_small_banner(edict_t *ent)
{
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_NOT;
ent->s.modelindex = gi.modelindex("models/ctf/banner/small.md2");
if (ent->spawnflags & 1) // team2
ent->s.skinnum = 1;
ent->s.frame = rand() % 16;
gi.linkentity(ent);
ent->think = misc_ctf_banner_think;
ent->nextthink = level.time + FRAMETIME;
}
/*-----------------------------------------------------------------------*/
void SetLevelName(pmenu_t *p)
{
static char levelname[33];
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;
p->text = levelname;
}
/*-----------------------------------------------------------------------*/
void DoRespawn(edict_t *ent);
void CTFResetAllPlayers(void)
{
edict_t *ent;
int i;
for (i = 1; i <= (int)maxclients->value; i++)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
if (ent->client->menu)
PMenu_Close(ent);
CTFPlayerResetGrapple(ent);
//CW++
if ((sv_gametype->value != G_FFA))
{
//CW--
CTFDeadDropFlag(ent);
CTFDeadDropTech(ent);
ent->client->resp.ctf_team = CTF_NOTEAM;
}
//CW++
else
ent->client->resp.ctf_team = CTF_TEAM_FFA;
if (ent->isabot)
{
CTFAssignTeam(ent->client);
ent->client->resp.ready = true;
ent->client->routetrace = false;
ent->client->respawn_time = level.time;
ent->nextthink = level.time + FRAMETIME;
ent->s.modelindex2 = 0;
ent->s.modelindex3 = 0;
ent->s.modelindex4 = 0;
}
else
//CW--
ent->client->resp.ready = false;
ent->svflags = 0;
ent->flags &= ~FL_GODMODE;
PutClientInServer(ent);
}
//CW++
if (sv_gametype->value == G_CTF)
{
//CW--
CTFResetTech();
CTFResetFlags();
}
for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++)
{
if (ent->inuse && !ent->client)
{
if ((ent->solid == SOLID_NOT) && (ent->think == DoRespawn) && (ent->nextthink >= level.time))
{
ent->nextthink = 0;
DoRespawn(ent);
}
}
}
if (teamgame.match == MATCH_SETUP)
teamgame.matchtime = level.time + (matchsetuptime->value * 60.0);
}
void CTFAssignGhost(edict_t *ent)
{
int ghost;
int i;
for (ghost = 0; ghost < MAX_CLIENTS; ghost++)
{
if (!teamgame.ghosts[ghost].code)
break;
}
if (ghost == MAX_CLIENTS)
return;
teamgame.ghosts[ghost].team = ent->client->resp.ctf_team;
teamgame.ghosts[ghost].score = 0;
for (;;)
{
teamgame.ghosts[ghost].code = 10000 + (rand() % 90000);
for (i = 0; i < MAX_CLIENTS; i++)
{
if ((i != ghost) && (teamgame.ghosts[i].code == teamgame.ghosts[ghost].code))
break;
}
if (i == MAX_CLIENTS)
break;
}
teamgame.ghosts[ghost].ent = ent;
// strcpy(teamgame.ghosts[ghost].netname, ent->client->pers.netname);
Com_strcpy(teamgame.ghosts[ghost].netname, sizeof(teamgame.ghosts[ghost].netname), ent->client->pers.netname);
ent->client->resp.ghost = teamgame.ghosts + ghost;
gi_cprintf(ent, PRINT_CHAT, "Your ghost code = %d\n", teamgame.ghosts[ghost].code);
gi_cprintf(ent, PRINT_HIGH, "Following loss of connection, rejoin with your score intact by typing \"ghost %d\"\n", teamgame.ghosts[ghost].code);
}
// start a match
void CTFStartMatch(void)
{
edict_t *ent;
int i;
teamgame.match = MATCH_GAME;
teamgame.matchtime = level.time + (matchtime->value * 60.0);
teamgame.countdown = false;
teamgame.team1 = teamgame.team2 = 0;
//CW++
teamgame.frags1 = teamgame.frags2 = 0;
//CW--
memset(teamgame.ghosts, 0, sizeof(teamgame.ghosts));
for (i = 1; i <= (int)maxclients->value; i++)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
ent->client->resp.score = 0;
ent->client->resp.ctf_state = 0;
ent->client->resp.ghost = NULL;
gi_centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************");
if (ent->client->resp.ctf_team != CTF_NOTEAM)
{
//CW++
if (ent->isabot)
{
ent->client->respawn_time = level.time + 1.0 + ((float)(rand()%30)/10.0);
ent->client->ps.pmove.pm_type = PM_DEAD;
ent->deadflag = DEAD_DEAD;
ent->svflags = SVF_NOCLIENT;
ent->health = 0;
}
else
{
//CW--
// make up a ghost code
CTFAssignGhost(ent);
CTFPlayerResetGrapple(ent);
ent->svflags = SVF_NOCLIENT;
ent->flags &= ~FL_GODMODE;
ent->client->respawn_time = level.time + 1.0 + ((float)(rand()%30)/10.0);
ent->client->ps.pmove.pm_type = PM_DEAD;
ent->client->anim_priority = ANIM_DEATH;
ent->s.frame = FRAME_death308-1;
ent->client->anim_end = FRAME_death308;
ent->deadflag = DEAD_DEAD;
ent->movetype = MOVETYPE_NOCLIP;
ent->client->ps.gunindex = 0;
gi.linkentity(ent);
}
}
}
}
void CTFEndMatch(void)
{
teamgame.match = MATCH_POST;
gi_bprintf(PRINT_CHAT, "MATCH COMPLETED!\n");
CTFCalcScores();
//CW++
if (sv_gametype->value == G_TDM)
{
gi_bprintf(PRINT_HIGH, "%s TEAM: %d points\n", sv_team1_name->string, teamgame.total1); //CW
gi_bprintf(PRINT_HIGH, "%s TEAM: %d points\n", sv_team2_name->string, teamgame.total2); //CW
}
else
{
//CW--
gi_bprintf(PRINT_HIGH, "%s TEAM: %d captures, %d points\n",
sv_team1_name->string, teamgame.team1, teamgame.total1); //CW
gi_bprintf(PRINT_HIGH, "%s TEAM: %d captures, %d points\n",
sv_team2_name->string, teamgame.team2, teamgame.total2); //CW
}
if (teamgame.team1 > teamgame.team2)
gi_bprintf(PRINT_CHAT, "%s team won over the %s team by %d CAPTURES!\n",
sv_team1_name->string, sv_team2_name->string, teamgame.team1 - teamgame.team2); //CW
else if (teamgame.team2 > teamgame.team1)
gi_bprintf(PRINT_CHAT, "%s team won over the %s team by %d CAPTURES!\n",
sv_team2_name->string, sv_team1_name->string, teamgame.team2 - teamgame.team1); //CW
else if (teamgame.total1 > teamgame.total2) // frag tie breaker
gi_bprintf(PRINT_CHAT, "%s team won over the %s team by %d POINTS!\n",
sv_team1_name->string, sv_team2_name->string, teamgame.total1 - teamgame.total2); //CW
else if (teamgame.total2 > teamgame.total1)
gi_bprintf(PRINT_CHAT, "%s team won over the %s team by %d POINTS!\n",
sv_team2_name->string, sv_team1_name->string, teamgame.total2 - teamgame.total1); //CW
else
gi_bprintf(PRINT_CHAT, "TIE GAME!\n");
EndDMLevel();
}
qboolean CTFNextMap(void)
{
if (teamgame.match == MATCH_POST)
{
teamgame.match = MATCH_SETUP;
CTFResetAllPlayers();
return true;
}
return false;
}
void CTFReady(edict_t *ent)
{
edict_t *e;
int i;
int j;
int t1;
int t2;
if (ent->client->resp.ctf_team == CTF_NOTEAM)
{
gi_cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
return;
}
if (teamgame.match != MATCH_SETUP)
{
gi_cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
return;
}
if (ent->client->resp.ready)
{
gi_cprintf(ent, PRINT_HIGH, "You have already committed.\n");
return;
}
ent->client->resp.ready = true;
gi_bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname);
t1 = t2 = 0;
for (j = 0, i = 1; i <= (int)maxclients->value; i++)
{
e = g_edicts + i;
if (!e->inuse)
continue;
if ((e->client->resp.ctf_team != CTF_NOTEAM) && !e->client->resp.ready)
j++;
if (e->client->resp.ctf_team == CTF_TEAM1)
t1++;
else if (e->client->resp.ctf_team == CTF_TEAM2)
t2++;
}
if (!j && t1 && t2)
{
// everyone has committed
gi_bprintf(PRINT_CHAT, "All players have committed. Match starting.\n");
teamgame.match = MATCH_PREGAME;
teamgame.matchtime = level.time + matchstarttime->value;
teamgame.countdown = false;
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
}
}
void CTFNotReady(edict_t *ent)
{
if (ent->client->resp.ctf_team == CTF_NOTEAM)
{
gi_cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
return;
}
if (teamgame.match != MATCH_SETUP && teamgame.match != MATCH_PREGAME)
{
gi_cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
return;
}
if (!ent->client->resp.ready)
{
gi_cprintf(ent, PRINT_HIGH, "You haven't committed.\n");
return;
}
ent->client->resp.ready = false;
gi_bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname);
if (teamgame.match == MATCH_PREGAME)
{
gi_bprintf(PRINT_CHAT, "Match halted.\n");
teamgame.match = MATCH_SETUP;
teamgame.matchtime = level.time + (matchsetuptime->value * 60.0);
}
}
void CTFGhost(edict_t *ent)
{
int i;
int n;
if (gi.argc() < 2)
{
gi_cprintf(ent, PRINT_HIGH, "Usage: ghost <code>\n");
return;
}
if (ent->client->resp.ctf_team != CTF_NOTEAM)
{
gi_cprintf(ent, PRINT_HIGH, "You are already in the game.\n");
return;
}
if (teamgame.match != MATCH_GAME)
{
gi_cprintf(ent, PRINT_HIGH, "No match is in progress.\n");
return;
}
n = atoi(gi.argv(1));
for (i = 0; i < MAX_CLIENTS; i++)
{
if (teamgame.ghosts[i].code && (teamgame.ghosts[i].code == n))
{
gi_cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n");
teamgame.ghosts[i].ent->client->resp.ghost = NULL;
ent->client->resp.ctf_team = teamgame.ghosts[i].team;
ent->client->resp.ghost = teamgame.ghosts + i;
ent->client->resp.score = teamgame.ghosts[i].score;
ent->client->resp.ctf_state = 0;
teamgame.ghosts[i].ent = ent;
ent->svflags = 0;
ent->flags &= ~FL_GODMODE;
//CW++
ent->client->spectator = false;
//CW--
PutClientInServer(ent);
gi_bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n", ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team));
return;
}
}
gi_cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n");
}
qboolean CTFMatchSetup(void)
{
if ((teamgame.match == MATCH_SETUP) || (teamgame.match == MATCH_PREGAME))
return true;
return false;
}
qboolean CTFMatchOn(void)
{
if (teamgame.match == MATCH_GAME)
return true;
return false;
}
/*-----------------------------------------------------------------------*/
void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p);
void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p);
void CTFCredits(edict_t *ent, pmenuhnd_t *p);
void CTFChaseCam(edict_t *ent, pmenuhnd_t *p);
//CW++
void AWKCredits1(edict_t *ent, pmenuhnd_t *p);
void AWKCredits2(edict_t *ent, pmenuhnd_t *p);
void AWKCredits3(edict_t *ent, pmenuhnd_t *p);
void AWKCredits4(edict_t *ent, pmenuhnd_t *p);
void AWKCredits5(edict_t *ent, pmenuhnd_t *p);
void AWKJoinGame(edict_t *ent, pmenuhnd_t *p);
//CW--
pmenu_t creditsmenu[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits (ThreeWave CTF)", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Programming", PMENU_ALIGN_CENTER, NULL },
{ "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL },
{ "*Level Design", PMENU_ALIGN_CENTER, NULL },
{ "Christian Antkow", PMENU_ALIGN_CENTER, NULL },
{ "Tim Willits", PMENU_ALIGN_CENTER, NULL },
{ "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL },
{ "*Art", PMENU_ALIGN_CENTER, NULL },
{ "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, NULL },
{ "Kevin Cloud", PMENU_ALIGN_CENTER, NULL },
{ "*Sound", PMENU_ALIGN_CENTER, NULL },
{ "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, NULL },
{ "*Original CTF Art Design", PMENU_ALIGN_CENTER, NULL },
{ "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
};
//CW++
pmenu_t creditsmenu_awk_1[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Original Concept and Design", PMENU_ALIGN_CENTER, NULL },
{ "Eric 'Redchurch' v Rothkirch", PMENU_ALIGN_CENTER, NULL },
{ "Patrick Martin", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Programming", PMENU_ALIGN_CENTER, NULL },
{ "Chris 'Musashi' Walker", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Additional Programming", PMENU_ALIGN_CENTER, NULL },
{ "Philip 'Maj.Bitch' Blair", PMENU_ALIGN_CENTER, NULL },
{ "Doug 'Raven' Buckley", PMENU_ALIGN_CENTER, NULL },
{ "David Hyde", PMENU_ALIGN_CENTER, NULL },
{ "David 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL },
{ "'Ponpoko'", PMENU_ALIGN_CENTER, NULL },
{ "Richard 'r1ch' Stanway", PMENU_ALIGN_CENTER, NULL },
{ "More...", PMENU_ALIGN_LEFT, AWKCredits2 }
};
pmenu_t creditsmenu_awk_2[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*3D Models & Animation", PMENU_ALIGN_CENTER, NULL },
{ "Ben 'Flecko' Foote", PMENU_ALIGN_CENTER, NULL },
{ "Jeff 'Stecki' Garstecki", PMENU_ALIGN_CENTER, NULL },
{ "Rikki 'Phukymoto' Knight", PMENU_ALIGN_CENTER, NULL },
{ "John 'Malekyth' Sheffield", PMENU_ALIGN_CENTER, NULL },
{ "Tony 'Crash-S9' Westerlund", PMENU_ALIGN_CENTER, NULL },
{ "Alex 'Strykerwolf' Wright", PMENU_ALIGN_CENTER, NULL },
{ "Lee David Ash", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Sound & Voice Effects", PMENU_ALIGN_CENTER, NULL },
{ "Eric 'Redchurch' v Rothkirch", PMENU_ALIGN_CENTER, NULL },
{ "Mike 'Awesund' Kaminski", PMENU_ALIGN_CENTER, NULL },
{ "Jake 'Snake' Schytt", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "More...", PMENU_ALIGN_LEFT, AWKCredits3 }
};
pmenu_t creditsmenu_awk_3[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*2D Skin Art", PMENU_ALIGN_CENTER, NULL },
{ "Francois-Xavier Delmotte", PMENU_ALIGN_CENTER, NULL },
{ "Jeff 'Stecki' Garstecki", PMENU_ALIGN_CENTER, NULL },
{ "Christopher Greenhaw", PMENU_ALIGN_CENTER, NULL },
{ "Chris 'Shatter' Holden", PMENU_ALIGN_CENTER, NULL },
{ "Rikki 'Phukymoto' Knight", PMENU_ALIGN_CENTER, NULL },
{ "Tony 'Crash-S9' Westerlund", PMENU_ALIGN_CENTER, NULL },
{ "Alex 'Strykerwolf' Wright", PMENU_ALIGN_CENTER, NULL },
{ "Lee David Ash", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "More...", PMENU_ALIGN_LEFT, AWKCredits4 }
};
pmenu_t creditsmenu_awk_4[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Level Design", PMENU_ALIGN_CENTER, NULL },
{ "Shon 'Maric' Shaffer", PMENU_ALIGN_CENTER, NULL },
{ "Rob 'Panzer' Berwick", PMENU_ALIGN_CENTER, NULL },
{ "Eric 'Sherminator' Sherman", PMENU_ALIGN_CENTER, NULL },
{ "Steve 'Jester' Veihl", PMENU_ALIGN_CENTER, NULL },
{ "Chris 'Musashi' Walker", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Website Design", PMENU_ALIGN_CENTER, NULL },
{ "Alex 'sonNeh' Bunskoek", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "More...", PMENU_ALIGN_LEFT, AWKCredits5 }
};
pmenu_t creditsmenu_awk_5[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Credits", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "*Testing & Gameplay Concepts", PMENU_ALIGN_CENTER, NULL },
{ "Pete 'Wizkid' Duhamel", PMENU_ALIGN_CENTER, NULL },
{ "Jake 'Snake' Schytt", PMENU_ALIGN_CENTER, NULL },
{ "Shon 'Maric' Shaffer", PMENU_ALIGN_CENTER, NULL },
{ "Eric 'Sherminator' Sherman", PMENU_ALIGN_CENTER, NULL },
{ "Mike 'Quiet' Berger", PMENU_ALIGN_CENTER, NULL },
{ "Paul 'Bass' Boblett", PMENU_ALIGN_CENTER, NULL },
{ "Carl 'CyberGhost' Ewing", PMENU_ALIGN_CENTER, NULL },
{ "Gene 'Wild' Kaufman", PMENU_ALIGN_CENTER, NULL },
{ "Chris 'Death' Sandy", PMENU_ALIGN_CENTER, NULL },
{ "Cassie 'Mistery' Scott", PMENU_ALIGN_CENTER, NULL },
{ "Michael 'Ledhead' Smock", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "More...", PMENU_ALIGN_LEFT, CTFCredits }
};
//CW--
//CW++
static const int jmenu_game = 1;
//CW--
static const int jmenu_level = 2;
static const int jmenu_match = 3;
static const int jmenu_red = 5;
static const int jmenu_blue = 7;
static const int jmenu_chase = 9;
pmenu_t joinmenu[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Gametype : ThreeWave CTF", PMENU_ALIGN_CENTER, NULL }, //CW
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "Join Red Team", PMENU_ALIGN_LEFT, CTFJoinTeam1 },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Join Blue Team", PMENU_ALIGN_LEFT, CTFJoinTeam2 },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Chase Camera", PMENU_ALIGN_LEFT, CTFChaseCam },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Credits", PMENU_ALIGN_LEFT, AWKCredits1 }, //CW
{ "Message Of The Day", PMENU_ALIGN_LEFT, ShowMOTD }, //CW
{ "Help", PMENU_ALIGN_LEFT, GMenu_Help }, //CW
{ NULL, PMENU_ALIGN_LEFT, NULL }, //CW
{ "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL },
{ "ENTER to select", PMENU_ALIGN_LEFT, NULL },
{ "v" AWK_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL },
};
//CW++
static const int jmenu_ffa_level = 2;
pmenu_t joinmenu_ffa[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Gametype : FFA", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "Join Game", PMENU_ALIGN_LEFT, AWKJoinGame },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Chase Camera", PMENU_ALIGN_LEFT, CTFChaseCam },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Credits", PMENU_ALIGN_LEFT, AWKCredits1 },
{ "Message Of The Day", PMENU_ALIGN_LEFT, ShowMOTD },
{ "Help", PMENU_ALIGN_LEFT, GMenu_Help },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL },
{ "ENTER to select", PMENU_ALIGN_LEFT, NULL },
{ "v" AWK_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL }
};
static const int ncmenu_game = 1;
//CW--
pmenu_t nochasemenu[] = {
{ "*- AWAKENING II -", PMENU_ALIGN_CENTER, NULL },
{ "*Gametype : ThreeWave CTF", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "No one to chase", PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ "Return to main menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
};
void CTFJoinTeam(edict_t *ent, int desired_team)
{
char *s;
PMenu_Close(ent);
ent->svflags &= ~SVF_NOCLIENT;
ent->client->resp.ctf_team = desired_team;
ent->client->resp.ctf_state = 0;
s = Info_ValueForKey(ent->client->pers.userinfo, "skin");
CTFAssignSkin(ent, s);
// Assign a ghost if we are in match mode.
if (teamgame.match == MATCH_GAME)
{
if (ent->client->resp.ghost)
ent->client->resp.ghost->code = 0;
ent->client->resp.ghost = NULL;
CTFAssignGhost(ent);
}
//CW++
ent->client->spectator = false;
ent->client->chase_target = NULL;
//CW--
PutClientInServer(ent);
// Add a teleportation effect, and hold player in place briefly.
ent->s.event = EV_PLAYER_TELEPORT;
ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
ent->client->ps.pmove.pm_time = 14;
gi_bprintf(PRINT_HIGH, "%s joined the %s team.\n", ent->client->pers.netname, CTFTeamName(desired_team));
if (teamgame.match == MATCH_SETUP)
{
gi_centerprintf(ent, "***********************\n"
"Type \"ready\" in console\n"
"to ready up.\n"
"***********************");
}
}
void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p)
{
CTFJoinTeam(ent, CTF_TEAM1);
}
void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p)
{
CTFJoinTeam(ent, CTF_TEAM2);
}
void CTFChaseCam(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
ChaseHelp(ent);
}
void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
CTFOpenJoinMenu(ent);
}
void DeathmatchScoreboard(edict_t *ent);
void CTFShowScores(edict_t *ent, pmenu_t *p)
{
PMenu_Close(ent);
ent->client->showscores = true;
ent->client->showinventory = false;
DeathmatchScoreboard(ent);
}
int CTFUpdateJoinMenu(edict_t *ent)
{
static char team1players[32];
static char team2players[32];
char msgbuf[32]; //CW++
int num1;
int num2;
int i;
if ((teamgame.match >= MATCH_PREGAME) && matchlock->value)
{
joinmenu[jmenu_red].text = "MATCH IS LOCKED";
joinmenu[jmenu_red].SelectFunc = NULL;
joinmenu[jmenu_blue].text = " (entry is not permitted)";
joinmenu[jmenu_blue].SelectFunc = NULL;
}
else
{
if (teamgame.match >= MATCH_PREGAME)
{
//CW++
Com_sprintf(msgbuf, sizeof(msgbuf), "Join %s MATCH Team", sv_team1_name->string);
joinmenu[jmenu_red].text = strdup(msgbuf);
Com_sprintf(msgbuf, sizeof(msgbuf), "Join %s MATCH Team", sv_team2_name->string);
joinmenu[jmenu_blue].text = strdup(msgbuf);
//CW--
}
else
{
//CW++
Com_sprintf(msgbuf, sizeof(msgbuf), "Join %s Team", sv_team1_name->string);
joinmenu[jmenu_red].text = strdup(msgbuf);
Com_sprintf(msgbuf, sizeof(msgbuf), "Join %s Team", sv_team2_name->string);
joinmenu[jmenu_blue].text = strdup(msgbuf);
//CW--
}
joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1;
joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2;
}
if (ctf_forcejoin->string && *ctf_forcejoin->string)
{
if (Q_stricmp(ctf_forcejoin->string, "red") == 0)
{
joinmenu[jmenu_blue].text = NULL;
joinmenu[jmenu_blue].SelectFunc = NULL;
}
else if (Q_stricmp(ctf_forcejoin->string, "blue") == 0)
{
joinmenu[jmenu_red].text = NULL;
joinmenu[jmenu_red].SelectFunc = NULL;
}
//CW++
else if (Q_stricmp(ctf_forcejoin->string, sv_team1_name->string))
{
joinmenu[jmenu_blue].text = NULL;
joinmenu[jmenu_blue].SelectFunc = NULL;
}
else if (Q_stricmp(ctf_forcejoin->string, sv_team2_name->string))
{
joinmenu[jmenu_red].text = NULL;
joinmenu[jmenu_red].SelectFunc = NULL;
}
//CW--
}
joinmenu[jmenu_chase].text = "Chase Camera (Help)";
SetLevelName(joinmenu + jmenu_level);
num1 = num2 = 0;
for (i = 0; i < (int)maxclients->value; i++)
{
if (!g_edicts[i+1].inuse)
continue;
if (game.clients[i].resp.ctf_team == CTF_TEAM1)
num1++;
else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
num2++;
}
Com_sprintf(team1players, sizeof(team1players), " (%d players)", num1);
Com_sprintf(team2players, sizeof(team2players), " (%d players)", num2);
switch (teamgame.match)
{
case MATCH_NONE :
joinmenu[jmenu_match].text = NULL;
break;
case MATCH_SETUP :
joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS";
break;
case MATCH_PREGAME :
joinmenu[jmenu_match].text = "*MATCH STARTING";
break;
case MATCH_GAME :
joinmenu[jmenu_match].text = "*MATCH IN PROGRESS";
break;
//CW++
case MATCH_POST:
joinmenu[jmenu_match].text = "*MATCH COMPLETED";
break;
//CW--
}
if (joinmenu[jmenu_red].text)
joinmenu[jmenu_red+1].text = team1players;
else
joinmenu[jmenu_red+1].text = NULL;
if (joinmenu[jmenu_blue].text)
joinmenu[jmenu_blue+1].text = team2players;
else
joinmenu[jmenu_blue+1].text = NULL;
if (num1 > num2)
return CTF_TEAM1;
else if (num2 > num1)
return CTF_TEAM2;
return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2;
}
void CTFOpenJoinMenu(edict_t *ent)
{
int team;
//CW++
if (ent->client->menu)
PMenu_Close(ent);
if (sv_gametype->value == G_FFA)
{
SetLevelName(joinmenu_ffa + jmenu_ffa_level);
PMenu_Open(ent, joinmenu_ffa, -1, sizeof(joinmenu_ffa) / sizeof(pmenu_t), NULL);
return;
}
else if (sv_gametype->value == G_TDM)
joinmenu[jmenu_game].text = "*Gametype : Team-DM";
else if (sv_gametype->value == G_ASLT)
joinmenu[jmenu_game].text = "*Gametype : Assault";
//CW--
team = CTFUpdateJoinMenu(ent);
if (ent->client->chase_target)
team = 8;
else if (team == CTF_TEAM1)
team = 4;
else
team = 6;
PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL);
}
void CTFCredits(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL);
}
//CW++
void AWKCredits1(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu_awk_1, -1, sizeof(creditsmenu_awk_1) / sizeof(pmenu_t), NULL);
}
void AWKCredits2(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu_awk_2, -1, sizeof(creditsmenu_awk_2) / sizeof(pmenu_t), NULL);
}
void AWKCredits3(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu_awk_3, -1, sizeof(creditsmenu_awk_3) / sizeof(pmenu_t), NULL);
}
void AWKCredits4(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu_awk_4, -1, sizeof(creditsmenu_awk_4) / sizeof(pmenu_t), NULL);
}
void AWKCredits5(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
PMenu_Open(ent, creditsmenu_awk_5, -1, sizeof(creditsmenu_awk_5) / sizeof(pmenu_t), NULL);
}
void AWKJoinGame(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
ent->svflags &= ~SVF_NOCLIENT;
ent->client->resp.ctf_team = CTF_TEAM_FFA;
ent->client->resp.ctf_state = 0;
ent->client->chase_target = NULL;
ent->client->spectator = false;
PutClientInServer(ent);
// Add a teleportation effect, and briefly hold the player in place.
ent->s.event = EV_PLAYER_TELEPORT;
ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
ent->client->ps.pmove.pm_time = 14;
gi_bprintf(PRINT_HIGH, "%s has joined the fray\n", ent->client->pers.netname);
}
void e_menu_on(edict_t *self)
{
if (!self->owner->client->menu)
CTFOpenJoinMenu(self->owner);
G_FreeEdict(self);
}
//CW--
qboolean CTFStartClient(edict_t *ent)
{
//CW++
edict_t *e_menu;
if (ent->isabot)
return false;
//CW--
if (ent->client->resp.ctf_team != CTF_NOTEAM)
return false;
if (!((int)dmflags->value & DF_FORCEJOIN) || (teamgame.match >= MATCH_SETUP))
{
// Start as an observer.
ent->movetype = MOVETYPE_NOCLIP;
ent->solid = SOLID_NOT;
ent->svflags |= SVF_NOCLIENT;
ent->client->resp.ctf_team = CTF_NOTEAM;
ent->client->ps.gunindex = 0;
//CW++
ent->client->chase_target = NULL;
ent->client->spectator = true;
//CW--
gi.linkentity(ent);
//CW++
e_menu = G_Spawn();
e_menu->classname = "menu_on";
e_menu->svflags |= SVF_NOCLIENT;
e_menu->owner = ent;
e_menu->think = e_menu_on;
e_menu->nextthink = level.time + 1.0 + random();
//CW--
return true;
}
return false;
}
void CTFObserver(edict_t *ent)
{
char userinfo[MAX_INFO_STRING];
//CW++
edict_t *index;
edict_t *check;
qboolean finished = false;
int i;
if (ent->client->spectator)
return;
// If the player is using a spycam, bring them out of it first.
if (ent->client->spycam)
camera_off(ent);
//CW--
CTFPlayerResetGrapple(ent);
CTFDeadDropFlag(ent);
CTFDeadDropTech(ent);
//CW++
gi_bprintf(PRINT_HIGH, "%s is watching from the sidelines.\n", ent->client->pers.netname);
// Clear all our weapon entities, and droptofloor any spikes stuck in us.
for (i = 0; i < globals.num_edicts; ++i)
{
check = &g_edicts[i];
if (!check->inuse)
continue;
if (((check->owner == ent) || (check->oldenemy == ent)) && check->wep_proj)
{
G_FreeEdict(check);
continue;
}
if ((check->think == Spike_MoveWithEnt) && (check->enemy == ent))
{
check->enemy = NULL;
G_FreeEdict(check);
continue;
}
if ((check->die == Trap_DieFromDamage) && (check->enemy == ent))
{
Trap_Die(check);
continue;
}
}
// Snuff out flames if player is on fire.
if (ent->burning)
{
ent->burning = false;
if (ent->flame) // sanity check
{
ent->flame->touch = NULL;
Flame_Expire(ent->flame);
}
}
// Search through the player's linked list of Trap and C4 entities (if any), and pop them.
if (ent->next_node)
{
index = ent->next_node;
while (index && !finished)
{
check = index;
if (index->next_node)
index = index->next_node;
else
finished = true;
if (check->die == C4_DieFromDamage)
C4_Die(check);
else if (check->die == Trap_DieFromDamage)
Trap_Die(check);
else
gi.dprintf("BUG: Invalid next_node pointer in CTFObserver().\nPlease contact musashi@planetquake.com\n");
}
}
// Reset remaining client data.
ent->client->hook_on = false;
ent->tractored = false;
ent->disintegrated = false;
ent->client->mod_changeteam = false;
ent->client->spectator = true;
ent->client->quad_framenum = 0;
ent->client->invincible_framenum = 0;
ent->client->breather_framenum = 0;
ent->client->enviro_framenum = 0;
ent->client->antibeam_framenum = 0;
ent->client->frozen_framenum = 0;
ent->client->siphon_framenum = 0;
ent->client->needle_framenum = 0;
ent->client->haste_framenum = 0;
ent->client->grenade_blew_up = false;
ent->client->grenade_time = 0;
ent->client->show_gausscharge = false;
ent->client->show_gausstarget = 0;
ent->client->gauss_dmg = 0;
ent->client->gauss_framenum = 0;
ent->client->agm_charge = 0;
ent->client->agm_showcharge = false;
ent->client->agm_tripped = false;
ent->client->agm_on = false;
ent->client->agm_push = false;
ent->client->agm_pull = false;
ent->client->held_by_agm = false;
ent->client->flung_by_agm = false;
ent->client->thrown_by_agm = false;
ent->client->agm_enemy = NULL;
if (ent->client->agm_target != NULL)
{
ent->client->agm_target->client->held_by_agm = false;
ent->client->agm_target->client->flung_by_agm = false;
ent->client->agm_target = NULL;
}
ent->client->resp.ready = false;
//CW--
ent->deadflag = DEAD_NO;
ent->movetype = MOVETYPE_NOCLIP;
ent->solid = SOLID_NOT;
ent->svflags |= SVF_NOCLIENT;
ent->client->resp.ctf_team = CTF_NOTEAM;
ent->client->ps.gunindex = 0;
ent->client->resp.score = 0;
memcpy(userinfo, ent->client->pers.userinfo, sizeof(userinfo));
InitClientPersistant(ent->client);
ClientUserinfoChanged(ent, userinfo);
gi.linkentity(ent);
CTFOpenJoinMenu(ent);
}
qboolean CTFInMatch(void)
{
if (teamgame.match > MATCH_NONE)
return true;
return false;
}
qboolean CTFCheckRules(void)
{
edict_t *ent;
char text[64];
char msg[64]; //CW++
int t;
int i;
int j;
if (teamgame.match != MATCH_NONE)
{
t = (int)(teamgame.matchtime - level.time);
// no team warnings in match mode
teamgame.warnactive = 0;
if (t <= 0) // time ended on something
{
switch (teamgame.match)
{
//CW++
case MATCH_NONE:
return false;
//CW--
case MATCH_SETUP :
// go back to normal mode
if (competition->value < 3)
{
teamgame.match = MATCH_NONE;
gi.cvar_set("competition", "0"); //CW
CTFResetAllPlayers();
//CW++
EndDMLevel();
//CW--
}
else // reset the time
teamgame.matchtime = level.time + matchsetuptime->value * 60.0;
return false;
case MATCH_PREGAME :
// match started!
CTFStartMatch();
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0);
return false;
case MATCH_GAME :
// match ended!
CTFEndMatch();
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0);
return false;
//CW++
case MATCH_POST:
return false;
//CW--
}
}
if (t == teamgame.lasttime)
return false;
teamgame.lasttime = t;
switch (teamgame.match)
{
//CW++
case MATCH_NONE:
break;
//CW--
case MATCH_SETUP :
for (j = 0, i = 1; i <= (int)maxclients->value; i++)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
if ((ent->client->resp.ctf_team != CTF_NOTEAM) && !ent->client->resp.ready)
j++;
}
if (competition->value < 3)
Com_sprintf(text, sizeof(text), "%02d:%02d SETUP: %d not ready", (int)(t / 60), (int)(t % 60), j);
else
Com_sprintf(text, sizeof(text), "SETUP: %d not ready", j);
gi.configstring(CONFIG_CTF_MATCH, text);
break;
case MATCH_PREGAME :
Com_sprintf(text, sizeof(text), "%02d:%02d UNTIL START", (int)(t / 60), (int)(t % 60));
gi.configstring(CONFIG_CTF_MATCH, text);
if ((t <= 10) && !teamgame.countdown)
{
teamgame.countdown = true;
gi.positioned_sound(world->s.origin, world, CHAN_VOICE | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
}
break;
case MATCH_GAME:
Com_sprintf(text, sizeof(text), "%02d:%02d MATCH", (int)(t / 60), (int)(t % 60));
gi.configstring(CONFIG_CTF_MATCH, text);
if ((t <= 10) && !teamgame.countdown)
{
teamgame.countdown = true;
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
}
break;
//CW++
case MATCH_POST:
break;
//CW--
}
return false;
}
else
{
int team1 = 0;
int team2 = 0;
if (level.time == teamgame.lasttime)
return false;
teamgame.lasttime = level.time;
// this is only done in non-match (public) mode
if (warn_unbalanced->value)
{
// count up the team totals
for (i = 1; i <= (int)maxclients->value; i++)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
if (ent->client->resp.ctf_team == CTF_TEAM1)
team1++;
else if (ent->client->resp.ctf_team == CTF_TEAM2)
team2++;
}
if ((team1 - team2 >= 2) && (team2 >= 2))
{
if (teamgame.warnactive != CTF_TEAM1)
{
Com_sprintf(msg, sizeof(msg), "WARNING: %s has too many players", sv_team1_name->string); //CW++
teamgame.warnactive = CTF_TEAM1;
gi.configstring(CONFIG_CTF_TEAMINFO, msg); //CW
}
}
else if ((team2 - team1 >= 2) && (team1 >= 2))
{
if (teamgame.warnactive != CTF_TEAM2)
{
Com_sprintf(msg, sizeof(msg), "WARNING: %s has too many players", sv_team2_name->string); //CW++
teamgame.warnactive = CTF_TEAM2;
gi.configstring(CONFIG_CTF_TEAMINFO, msg); //CW
}
}
else
teamgame.warnactive = 0;
}
else
teamgame.warnactive = 0;
}
if ((int)capturelimit->value && ((teamgame.team1 >= capturelimit->value) || (teamgame.team2 >= capturelimit->value)))
{
gi_bprintf(PRINT_HIGH, "Capturelimit hit.\n");
return true;
}
return false;
}
/*--------------------------------------------------------------------------
* just here to help old map conversions
*--------------------------------------------------------------------------*/
static void old_teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *dest;
vec3_t forward;
int i;
if (!other->client)
return;
dest = G_Find(NULL, FOFS(targetname), self->target);
if (!dest)
{
gi_cprintf(other, PRINT_HIGH, "Couldn't find destination\n"); //CW
return;
}
//ZOID++
CTFPlayerResetGrapple(other);
//ZOID--
// unlink to make sure it can't possibly interfere with KillBox
gi.unlinkentity(other);
VectorCopy(dest->s.origin, other->s.origin);
VectorCopy(dest->s.origin, other->s.old_origin);
// clear the velocity and hold them in place briefly
VectorClear(other->velocity);
other->client->ps.pmove.pm_time = 160>>3;
other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
// draw the teleport splash at source and on the player
self->enemy->s.event = EV_PLAYER_TELEPORT;
other->s.event = EV_PLAYER_TELEPORT;
// set angles
for (i = 0; i < 3 ; i++)
other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
other->s.angles[PITCH] = 0.0;
other->s.angles[YAW] = dest->s.angles[YAW];
other->s.angles[ROLL] = 0.0;
VectorCopy(dest->s.angles, other->client->ps.viewangles);
VectorCopy(dest->s.angles, other->client->v_angle);
// give a little forward velocity
AngleVectors(other->client->v_angle, forward, NULL, NULL);
VectorScale(forward, 200.0, other->velocity);
// kill anything at the destination
if (!KillBox(other))
{
}
gi.linkentity(other);
}
/*QUAKED trigger_teleport (0.5 0.5 0.5) ?
Players touching this will be teleported
*/
void SP_trigger_teleport(edict_t *ent)
{
edict_t *s;
int i;
if (!ent->target)
{
gi.dprintf("trigger_teleporter without a target at %s.\n", vtos(ent->s.origin)); //CW
G_FreeEdict(ent);
return;
}
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_TRIGGER;
ent->touch = old_teleporter_touch;
gi.setmodel(ent, ent->model);
gi.linkentity(ent);
// noise maker and splash effect dude
s = G_Spawn();
ent->enemy = s;
for (i = 0; i < 3; i++)
s->s.origin[i] = ent->mins[i] + (0.5 * (ent->maxs[i] - ent->mins[i]));
s->s.sound = gi.soundindex("world/hum1.wav");
//CW++
s->classname = "tele_noise";
//CW--
gi.linkentity(s);
}
/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
Point trigger_teleports at these.
*/
void SP_info_teleport_destination(edict_t *ent)
{
ent->s.origin[2] += 16.0;
}
/*----------------------------------------------------------------*/
void CTFStats(edict_t *ent)
{
edict_t *e2;
ghost_t *g;
char st[80];
char text[1024];
int i;
int e;
size_t textLen;
*text = 0;
if (teamgame.match == MATCH_SETUP)
{
for (i = 1; i <= (int)maxclients->value; i++)
{
e2 = g_edicts + i;
if (!e2->inuse)
continue;
if (!e2->client->resp.ready && (e2->client->resp.ctf_team != CTF_NOTEAM))
{
Com_sprintf(st, sizeof(st), "%s is not ready.\n", e2->client->pers.netname);
if (strlen(text) + strlen(st) < sizeof(text) - 50)
Com_strcat(text, sizeof(text), st);
}
}
}
for (i = 0, g = teamgame.ghosts; i < MAX_CLIENTS; i++, g++)
{
if (g->ent)
break;
}
if (i == MAX_CLIENTS)
{
if (*text)
gi_cprintf(ent, PRINT_HIGH, "%s", text);
gi_cprintf(ent, PRINT_HIGH, "No statistics available.\n");
return;
}
Com_strcat(text, sizeof(text), " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n");
for (i = 0, g = teamgame.ghosts; i < MAX_CLIENTS; i++, g++)
{
if (!*g->netname)
continue;
if (g->deaths + g->kills == 0)
e = 50;
else
e = (int)(g->kills * 100 / (g->kills + g->deaths));
// sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n",
Com_sprintf(st, sizeof(st), "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n",
g->number,
g->netname,
g->score,
g->kills,
g->deaths,
g->basedef,
g->carrierdef,
e);
if (strlen(text) + strlen(st) > sizeof(text) - 50)
{
// sprintf(text+strlen(text), "And more...\n");
textLen = strlen(text);
Com_sprintf(text+textLen, sizeof(text)-textLen, "And more...\n");
gi_cprintf(ent, PRINT_HIGH, "%s", text);
return;
}
Com_strcat(text, sizeof(text), st);
}
gi_cprintf(ent, PRINT_HIGH, "%s", text);
}
void CTFPlayerList(edict_t *ent)
{
edict_t *e2;
char st[80];
char text[1024]; //CW: was 1400
int i;
size_t textLen;
// Display number, name, connect time, ping, score, and operator status.
*text = 0;
for (i = 1; i <= (int)maxclients->value; i++)
{
e2 = g_edicts + i;
if (!e2->inuse)
continue;
Com_sprintf(st, sizeof(st), "%3d %-16.16s %02d:%02d %4d %3d%s%s\n",
i,
e2->client->pers.netname,
(int)((level.framenum - e2->client->resp.enterframe) / 600),
(int)(((level.framenum - e2->client->resp.enterframe) % 600) * 0.1),
e2->client->ping,
e2->client->resp.score,
((teamgame.match == MATCH_SETUP) || (teamgame.match == MATCH_PREGAME)) ?
((e2->client->resp.ready) ? " (ready)" : " (notready)") : "",
//CW++
(e2->client->pers.op_status) ? " (OP)" : ((e2->isabot) ? "(Bot)" : ""));
//CW--
if (strlen(text) + strlen(st) > sizeof(text) - 50)
{
// sprintf(text+strlen(text), "And more...\n");
textLen = strlen(text);
Com_sprintf(text+textLen, sizeof(text)-textLen, "And more...\n");
gi_cprintf(ent, PRINT_HIGH, "%s", text);
return;
}
Com_strcat(text, sizeof(text), st);
}
gi_cprintf(ent, PRINT_HIGH, "%s", text);
}
void CTFBoot(edict_t *ent, qboolean ban) //CW
{
char *strbuf; //CW
//CW++
if (!ent->client->pers.op_status)
{
gi_cprintf(ent, PRINT_HIGH, "You are not an Operator.\n");
return;
}
//CW--
if (gi.argc() < 2)
{
//CW++
CTFPlayerList(ent);
gi_cprintf(ent, PRINT_HIGH, "Usage: %s <player_num>\n", (ban)?"op_ban":"op_kick");
//CW--
return;
}
//CW++
if (gi.argv(2) && *gi.argv(2))
strbuf = gi.argv(2);
else
strbuf = "";
Op_Boot(ent, ban, atoi(gi.argv(1)), strbuf);
//CW--
}
//CW++
void Op_Boot(edict_t *ent, qboolean ban, int pnum, char *reason)
{
edict_t *targ;
char text[80];
if ((pnum < 1) || (pnum > (int)maxclients->value))
{
gi_cprintf(ent, PRINT_HIGH, "Invalid player number.\n");
return;
}
targ = g_edicts + pnum;
if (!targ->inuse)
{
gi_cprintf(ent, PRINT_HIGH, "That player number is not connected.\n");
return;
}
//CW++
if (targ == ent)
{
gi_cprintf(ent, PRINT_HIGH, "You cannot %s yourself!\n", (ban)?"ban":"kick");
return;
}
if (targ->isabot)
ban = false;
gi_bprintf(PRINT_CHAT, "%s was %s by %s\n", targ->client->pers.netname, (ban)?"banned":"kicked", ent->client->pers.netname);
if (strlen(reason))
gi_bprintf(PRINT_CHAT, "REASON: %s\n", reason);
if (ban)
{
gi_cprintf(ent, PRINT_HIGH, "Banned IP = %s\n", targ->client->pers.ip);
gi.dprintf("-> Banned IP = %s\n", targ->client->pers.ip);
Com_sprintf(text, sizeof(text), "sv addip %s\n", targ->client->pers.ip);
gi.AddCommandString(text);
Com_sprintf(text, sizeof(text), "sv writeip\n");
gi.AddCommandString(text);
}
//CW--
if (targ->isabot)
RemoveBot(targ);
else
{
Com_sprintf(text, sizeof(text), "kick %d\n", pnum - 1);
gi.AddCommandString(text);
}
}
void CTFSetPowerUpEffect(edict_t *ent, int def)
{
if (ent->client->resp.ctf_team == CTF_TEAM1)
ent->s.effects |= EF_PENT; // red
else if (ent->client->resp.ctf_team == CTF_TEAM2)
ent->s.effects |= EF_QUAD; // blue
else
ent->s.effects |= def;
}
//CW++
//======================================================================
// Team-DM stuff
//======================================================================
char *tdm_statusbar =
"yb -24 "
// health
"xv 0 "
"hnum "
"xv 50 "
"pic 0 "
// ammo
"if 2 "
" xv 100 "
" anum "
" xv 150 "
" pic 2 "
"endif "
// armor
"if 4 "
" xv 200 "
" rnum "
" xv 250 "
" pic 4 "
"endif "
// selected item
"if 6 "
" xv 296 "
" pic 6 "
"endif "
"yb -50 "
// picked up item
"if 7 "
" xv 0 "
" pic 7 "
" xv 26 "
" yb -42 "
" stat_string 8 "
" yb -50 "
"endif "
// timer
"if 9 "
"xv 246 "
"num 3 10 " //CW
"xv 296 "
"pic 9 "
"endif "
// help / weapon icon
"if 11 "
"xv 150 " //CW
"pic 11 "
"endif "
// frags
"xr -50 "
"yt 2 "
"num 3 14 "
//tech
"yb -129 "
"if 26 "
"xr -26 "
"pic 26 "
"endif "
// red team
"yb -102 "
"if 17 "
"xr -26 "
"pic 17 "
"endif "
"xr -80 "
"num 3 18 "
//joined overlay
"if 22 "
"yb -104 "
"xr -28 "
"pic 22 "
"endif "
// blue team
"yb -75 "
"if 19 "
"xr -26 "
"pic 19 "
"endif "
"xr -80 "
"num 3 20 "
"if 23 "
"yb -77 "
"xr -28 "
"pic 23 "
"endif "
// have flag graph
"if 21 "
"yt 26 "
"xr -24 "
"pic 21 "
"endif "
// id view state
"if 27 "
"xv 112 "
"yb -58 "
"stat_string 27 "
"endif "
"if 29 "
"xv 96 "
"yb -58 "
"pic 29 "
"endif "
"if 28 "
"xl 0 "
"yb -78 "
"stat_string 28 "
"endif "
"if 30 "
"xl 0 "
"yb -88 "
"stat_string 30 "
"endif "
;
// =====================
// Precache TDM items
// =====================
void TDMPrecache(void)
{
imageindex_i_ctf1 = gi.imageindex("i_ctf1");
imageindex_i_ctf2 = gi.imageindex("i_ctf2");
imageindex_i_ctfj = gi.imageindex("i_ctfj");
imageindex_sbfctf1 = gi.imageindex("sbfctf1");
imageindex_sbfctf2 = gi.imageindex("sbfctf2");
imageindex_ctfsb1 = gi.imageindex("ctfsb1");
imageindex_ctfsb2 = gi.imageindex("ctfsb2");
}
// =====================
// TDMScoreboardMessage
// =====================
void TDMScoreboardMessage(edict_t *ent, edict_t *killer)
{
gclient_t *cl;
edict_t *cl_ent;
char entry[1024];
char string[1400];
int len;
int i, j, k, n;
int sorted[2][MAX_CLIENTS];
int sortedscores[2][MAX_CLIENTS];
int score;
int total[2];
int totalscore[2];
int last[2];
int team;
int maxsize = 1000;
size_t entryLen, stringLen;
// Sort the clients by team and score.
total[0] = total[1] = 0;
last[0] = last[1] = 0;
totalscore[0] = totalscore[1] = 0;
for (i = 0; i < game.maxclients; ++i)
{
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse)
continue;
if (game.clients[i].resp.ctf_team == CTF_TEAM1)
team = 0;
else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
team = 1;
else
continue; // unknown team?
score = game.clients[i].resp.score;
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];
}
// Print level name, and add the clients in the sorted order.
*string = 0;
len = 0;
// sprintf(string, "if 24 xv 8 yv 8 pic 24 endif "
Com_sprintf(string, sizeof(string),
"if 24 xv 8 yv 8 pic 24 endif "
"xv 40 yv 28 string \"%4d/%-3d\" "
"if 25 xv 168 yv 8 pic 25 endif "
"xv 200 yv 28 string \"%4d/%-3d\" ",
totalscore[0], total[0],
totalscore[1], total[1]);
len = (int)strlen(string);
for (i = 0; i < 16; ++i)
{
if ((i >= total[0]) && (i >= total[1]))
break;
*entry = 0;
// Left side.
if (i < total[0])
{
cl = &game.clients[sorted[0][i]];
cl_ent = g_edicts + 1 + sorted[0][i];
// sprintf(entry+strlen(entry),
entryLen = strlen(entry);
Com_sprintf(entry+entryLen, sizeof(entry)-entryLen,
"ctf 0 %d %d %d %d ",
42 + i * 8,
sorted[0][i],
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (maxsize - len > strlen(entry))
{
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
last[0] = i;
}
}
// Right side.
if (i < total[1])
{
cl = &game.clients[sorted[1][i]];
cl_ent = g_edicts + 1 + sorted[1][i];
// sprintf(entry+strlen(entry),
entryLen = strlen(entry);
Com_sprintf(entry+entryLen, sizeof(entry)-entryLen,
"ctf 160 %d %d %d %d ",
42 + i * 8,
sorted[1][i],
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (maxsize - len > strlen(entry))
{
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
last[1] = i;
}
}
}
// Put in spectators if we have enough room.
if (last[0] > last[1])
j = last[0];
else
j = last[1];
j = (j + 2) * 8 + 42;
k = n = 0;
if (maxsize - len > 50)
{
for (i = 0; i < (int)maxclients->value; ++i)
{
cl_ent = g_edicts + 1 + i;
cl = &game.clients[i];
if (!cl_ent->inuse || (cl_ent->solid != SOLID_NOT) || (cl_ent->client->resp.ctf_team != CTF_NOTEAM))
continue;
if (!k)
{
k = 1;
Com_sprintf(entry, sizeof(entry), "xv 0 yv %d string2 \"Spectators\" ", j);
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
j += 8;
}
// sprintf(entry+strlen(entry),
entryLen = strlen(entry);
Com_sprintf(entry+entryLen, sizeof(entry)-entryLen,
"ctf %d %d %d %d %d ",
(n & 1) ? 160 : 0, // x
j, // y
i, // playernum
cl->resp.score,
cl->ping > 999 ? 999 : cl->ping);
if (maxsize - len > strlen(entry))
{
Com_strcat(string, sizeof(string), entry);
len = (int)strlen(string);
}
if (n & 1)
j += 8;
++n;
}
}
// Indicate if we couldn't fit everyone in.
if (total[0] - last[0] > 1) {
// sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", 42 + (last[0]+1)*8, total[0] - last[0] - 1);
stringLen = strlen(string);
Com_sprintf(string + stringLen, sizeof(stringLen) - stringLen, "xv 8 yv %d string \"..and %d more\" ", 42 + (last[0]+1)*8, total[0] - last[0] - 1);
}
if (total[1] - last[1] > 1) {
// sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", 42 + (last[1]+1)*8, total[1] - last[1] - 1);
stringLen = strlen(string);
Com_sprintf(string + stringLen, sizeof(stringLen) - stringLen, "xv 168 yv %d string \"..and %d more\" ", 42 + (last[1]+1)*8, total[1] - last[1] - 1);
}
gi.WriteByte(svc_layout);
gi.WriteString(string);
}
//CW--
//CW++
//======================================================================
// Assault stuff
//======================================================================
char *aslt_statusbar =
"yb -24 "
// health
"xv 0 "
"hnum "
"xv 50 "
"pic 0 "
// ammo
"if 2 "
" xv 100 "
" anum "
" xv 150 "
" pic 2 "
"endif "
// armor
"if 4 "
" xv 200 "
" rnum "
" xv 250 "
" pic 4 "
"endif "
// selected item
"if 6 "
" xv 296 "
" pic 6 "
"endif "
"yb -50 "
// picked up item
"if 7 "
" xv 0 "
" pic 7 "
" xv 26 "
" yb -42 "
" stat_string 8 "
" yb -50 "
"endif "
// timer
"if 9 "
"xv 246 "
"num 3 10 "
"xv 296 "
"pic 9 "
"endif "
// help / weapon icon
"if 11 "
"xv 150 " //CW
"pic 11 "
"endif "
// frags
"xr -50 "
"yt 2 "
"num 3 14 "
// tech
"yb -129 "
"if 26 "
"xr -26 "
"pic 26 "
"endif "
// red team
"yb -102 "
"if 17 "
"xr -26 "
"pic 17 "
"endif "
"xr -80 "
"num 3 18 "
// joined overlay
"if 22 "
"yb -104 "
"xr -28 "
"pic 22 "
"endif "
// blue team
"yb -75 "
"if 19 "
"xr -26 "
"pic 19 "
"endif "
"xr -80 "
"num 3 20 "
"if 23 "
"yb -77 "
"xr -28 "
"pic 23 "
"endif "
// attacking team indicator
"if 21 "
"yb -104 "
"xr -28 "
"pic 21 "
"endif "
"if 31 "
"yb -77 "
"xr -28 "
"pic 31 "
"endif "
// id view state
"if 27 "
"xv 112 "
"yb -58 "
"stat_string 27 "
"endif "
"if 29 "
"xv 96 "
"yb -58 "
"pic 29 "
"endif "
"if 28 "
"xl 0 "
"yb -78 "
"stat_string 28 "
"endif "
"if 30 "
"xl 0 "
"yb -88 "
"stat_string 30 "
"endif "
;
// =====================
// Precache Assault items
// =====================
void ASLTPrecache(void)
{
imageindex_i_ctf1 = gi.imageindex("i_ctf1");
imageindex_i_ctf2 = gi.imageindex("i_ctf2");
imageindex_i_ctfj = gi.imageindex("i_ctfj");
imageindex_i_aslta = gi.imageindex("i_aslta");
imageindex_sbfctf1 = gi.imageindex("sbfctf1");
imageindex_sbfctf2 = gi.imageindex("sbfctf2");
imageindex_ctfsb1 = gi.imageindex("ctfsb1");
imageindex_ctfsb2 = gi.imageindex("ctfsb2");
}
void ASLTSpawn(void)
{
memset(&asltgame, 0, sizeof(asltgame));
asltgame.victory = false;
asltgame.spawn = 0;
if (competition->value > 1) // teamgame will have already been setup by this point
{
teamgame.match = MATCH_SETUP;
teamgame.matchtime = level.time + (matchsetuptime->value * 60.0);
}
// Swap attacking and defending sides after each game.
if (g_round % 2)
asltgame.t_attack = CTF_TEAM2;
else
asltgame.t_attack = CTF_TEAM1;
// Set end-of-level messages, if they have been defined in the worldspawn entity.
if (!world->inuse)
return;
if (world->message2)
asltgame.msg_attack = strdup(world->message2);
if (world->message3)
asltgame.msg_defend = strdup(world->message3);
}
qboolean ASLTCheckRules(void)
{
edict_t *ent;
char text[64];
char msg[64];
int team1 = 0;
int team2 = 0;
int t;
int i;
int j;
if (teamgame.match != MATCH_NONE)
{
t = (int)(teamgame.matchtime - level.time);
teamgame.warnactive = 0; // no team warnings in match mode
// Timer ended on something.
if (t <= 0)
{
switch (teamgame.match)
{
//CW++
case MATCH_NONE:
return false;
//CW--
case MATCH_SETUP :
if (competition->value < 3)
{
teamgame.match = MATCH_NONE;
gi.cvar_set("competition", "0");
CTFResetAllPlayers();
EndDMLevel();
}
else
teamgame.matchtime = level.time + (matchsetuptime->value * 60.0);
return false;
case MATCH_PREGAME :
CTFStartMatch();
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0);
return false;
case MATCH_GAME :
CTFEndMatch();
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0);
return false;
//CW++
case MATCH_POST:
return false;
//CW--
}
}
if (t == teamgame.lasttime)
return false;
teamgame.lasttime = t;
switch (teamgame.match)
{
//CW++
case MATCH_NONE:
break;
//CW--
case MATCH_SETUP :
for (j = 0, i = 1; i <= (int)maxclients->value; ++i)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
if ((ent->client->resp.ctf_team != CTF_NOTEAM) && !ent->client->resp.ready)
++j;
}
if (competition->value < 3)
Com_sprintf(text, sizeof(text), "%02d:%02d SETUP: %d not ready", (int)(t / 60), (int)(t % 60), j);
else
Com_sprintf(text, sizeof(text), "SETUP: %d not ready", j);
gi.configstring(CONFIG_CTF_MATCH, text);
break;
case MATCH_PREGAME :
Com_sprintf(text, sizeof(text), "%02d:%02d UNTIL START", (int)(t / 60), (int)(t % 60));
gi.configstring(CONFIG_CTF_MATCH, text);
if ((t <= 10) && !teamgame.countdown)
{
teamgame.countdown = true;
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
}
break;
case MATCH_GAME:
Com_sprintf(text, sizeof(text), "%02d:%02d MATCH", (int)(t / 60), (int)(t % 60));
gi.configstring(CONFIG_CTF_MATCH, text);
if ((t <= 10) && !teamgame.countdown)
{
teamgame.countdown = true;
gi.positioned_sound(world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
}
break;
//CW++
case MATCH_POST:
break;
//CW--
}
return false;
}
else // this is only done in non-match (public) mode
{
if (level.time == teamgame.lasttime)
return false;
teamgame.lasttime = level.time;
if (warn_unbalanced->value)
{
for (i = 1; i <= (int)maxclients->value; ++i)
{
ent = g_edicts + i;
if (!ent->inuse)
continue;
if (ent->client->resp.ctf_team == CTF_TEAM1)
++team1;
else if (ent->client->resp.ctf_team == CTF_TEAM2)
++team2;
}
if ((team1 - team2 >= 2) && (team2 >= 2))
{
if (teamgame.warnactive != CTF_TEAM1)
{
Com_sprintf(msg, sizeof(msg), "WARNING: %s has too many players", sv_team1_name->string);
teamgame.warnactive = CTF_TEAM1;
gi.configstring(CONFIG_CTF_TEAMINFO, msg);
}
}
else if ((team2 - team1 >= 2) && (team1 >= 2))
{
if (teamgame.warnactive != CTF_TEAM2)
{
Com_sprintf(msg, sizeof(msg), "WARNING: %s has too many players", sv_team2_name->string);
teamgame.warnactive = CTF_TEAM2;
gi.configstring(CONFIG_CTF_TEAMINFO, msg);
}
}
else
teamgame.warnactive = 0;
}
else
teamgame.warnactive = 0;
}
if (asltgame.victory) // attackers have won
return true;
return false;
}
/*
================
SelectASLTSpawnPoint
Go to an Assault spawn point.
================
*/
edict_t *SelectASLTSpawnPoint(edict_t *ent)
{
edict_t *point_ok[MAX_SPAWNS];
edict_t *point_tf[MAX_SPAWNS];
edict_t *spot = NULL;
char *cname;
qboolean attacker = false;
int num_ok = 0;
int num_tf = 0;
if (!ent->client->resp.ctf_state)
ent->client->resp.ctf_state++;
// Determine whether we're looking for an attacker's or a defender's spawn point.
switch (ent->client->resp.ctf_team)
{
case CTF_TEAM1:
if (asltgame.t_attack == CTF_TEAM1)
{
cname = "info_player_attack";
attacker = true;
}
else
cname = "info_player_defend";
break;
case CTF_TEAM2:
if (asltgame.t_attack == CTF_TEAM2)
{
cname = "info_player_attack";
attacker = true;
}
else
cname = "info_player_defend";
break;
default:
return SelectRandomDeathmatchSpawnPoint();
}
// Attacker: only respawn at any [info_player_attack] whose 'count' value is the same as the
// game 'spawn' value.
// Defender: as attacker, but can also respawn at any [info_player_defend] whose 'count' value
// is greater than the game 'spawn' value if its SPAWNFLAG_GT_COUNT spawnflag is set.
while ((spot = G_Find(spot, FOFS(classname), cname)) != NULL)
{
if (attacker)
{
if (spot->count != asltgame.spawn)
continue;
}
else
{
if (spot->count != asltgame.spawn)
{
if (!((spot->count > asltgame.spawn) && (spot->spawnflags & SPAWNFLAG_GT_COUNT)))
continue;
}
}
if (TeamPlayersRangeFromSpot(spot, ent->client->resp.ctf_team) < TELEFRAG_DIST)
{
point_tf[num_tf] = spot;
if (num_tf < MAX_SPAWNS - 1)
++num_tf;
}
else
{
point_ok[num_ok] = spot;
if (++num_ok == MAX_SPAWNS)
break;
}
}
if ((num_ok == 0) && (num_tf == 0))
{
gi.dprintf("INFO: No Assault spawn points for %s team.\n", (attacker)?"attacking":"defending");
return SelectRandomDeathmatchSpawnPoint();
}
if (num_ok)
return point_ok[rand() % num_ok];
return point_tf[rand() % num_tf];
}
/*
==================
ASLTShowMission
==================
*/
const int msnmenu_obj = 1;
const int msnmenu_first = 3;
const int msnmenu_last = 15;
const int msnmenu_cmd = 17;
typedef struct mission_info_s
{
char* next;
int obj;
qboolean first;
} mission_info_t;
void ASLTMissionNextObj(edict_t *ent, pmenuhnd_t *p);
void ASLTMissionClose(edict_t *ent, pmenuhnd_t *p);
void ASLTMissionUpdate(edict_t *ent, pmenuhnd_t *display);
pmenu_t mission_menu[] = {
{ "*Assault - Mission Briefing", PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_CENTER, NULL }, // objective #
{ NULL, PMENU_ALIGN_CENTER, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL }, // start of objective text
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, NULL },
{ NULL, PMENU_ALIGN_LEFT, ASLTMissionNextObj } // next page/close
};
void ASLTMissionNextObj(edict_t *ent, pmenuhnd_t *p)
{
mission_info_t *mission = p->arg;
++mission->obj;
ASLTMissionUpdate(ent, p);
}
void ASLTMissionClose(edict_t *ent, pmenuhnd_t *p)
{
PMenu_Close(ent);
}
void ASLTMissionUpdate(edict_t *ent, pmenuhnd_t *display)
{
mission_info_t *mission = display->arg;
edict_t *e = NULL;
char title[32];
char text[LINESIZE + 1];
char *buffer = "";
char *c = "";
int itext = 0;
int line;
int n_msg;
qboolean finished = false;
// Clear current display.
for (line = msnmenu_first; line <= msnmenu_last; ++line)
PMenu_UpdateEntry(display->entries + line, " ", PMENU_ALIGN_LEFT, NULL);
// Search through map entities until the appropriate [info_mission] is found.
while ((e = G_Find(e, FOFS(classname), "info_mission")) != NULL)
{
if (mission->first)
{
if (e->spawnflags & SPAWNFLAG_FIRST)
{
mission->first = false;
break;
}
else
continue;
}
else
{
if (e->targetname && !Q_stricmp(mission->next, e->targetname))
break;
else
continue;
}
}
if (e == NULL)
{
gi_centerprintf(ent, "Cannot find objective #%d for this map.\n", mission->obj);
PMenu_Close(ent);
return;
}
// Display the objective number as a subtitle.
Com_sprintf(title, sizeof(title), "*Objective #%d", mission->obj);
PMenu_UpdateEntry(display->entries + msnmenu_obj, title, PMENU_ALIGN_CENTER, NULL);
// Display the message of the current [info_mission].
n_msg = 1;
line = msnmenu_first;
while (!finished)
{
if (n_msg == 1)
buffer = strdup(e->message);
else if (n_msg == 2)
buffer = strdup(e->message2);
else if (n_msg == 3)
buffer = strdup(e->message3);
c = buffer;
while (*c != 0)
{
// Dump the line buffer to the menu if:
// (a) a line's worth of characters have been read into the buffer, or
// (b) a line feed is reached.
if ((itext > LINESIZE) || (*c == 10))
{
// Word-wrapping: if we're in the middle of a word, backtrack through the buffer until the
// first space is reached, and only dump the buffer to that point.
if ((*c != 32) && (*c != 10))
{
while ((*c != 32) && (itext > 0))
{
--c;
--itext;
}
// If the word is longer that one line, break it across to the next one.
if (*c != 32)
{
c += LINESIZE;
itext += LINESIZE + 1;
}
}
*c = 0;
*(text + itext) = 0;
PMenu_UpdateEntry(display->entries + line, text, PMENU_ALIGN_LEFT, NULL);
itext = 0;
++line;
++c;
// Insert consecutive line feeds if they're present in the message string.
while ((*c == 10) && (line < msnmenu_last))
{
++line;
++c;
}
// If there is a space at the start of a new line, ignore it.
if (*c == 32)
++c;
}
if (line > msnmenu_last)
break;
strncpy(text + itext++, c++, 1);
}
++n_msg;
if (((n_msg == 2) && !e->message2) || ((n_msg == 3) && !e->message3) || (n_msg == 4))
finished = true;
}
// If the end of the message string has been reached, dump any characters in the buffer to the display.
if (itext && !(line > msnmenu_last))
{
*c = 0;
*(text + itext) = 0;
PMenu_UpdateEntry(display->entries + line, text, PMENU_ALIGN_LEFT, NULL);
}
free(buffer);
// Set the "next objective" menu command if there is a target [info_mission] to follow.
if (e->target)
{
mission->next = e->target;
Com_sprintf(text, sizeof(text), "View next objective");
PMenu_UpdateEntry(display->entries + msnmenu_cmd, text, PMENU_ALIGN_LEFT, ASLTMissionNextObj);
}
else
{
mission->next = NULL;
Com_sprintf(text, sizeof(text), "End briefing");
PMenu_UpdateEntry(display->entries + msnmenu_cmd, text, PMENU_ALIGN_LEFT, ASLTMissionClose);
}
PMenu_Update(ent);
}
void ASLTShowMission(edict_t *ent)
{
mission_info_t *mission;
pmenuhnd_t *menu;
if (ent->client->menu)
PMenu_Close(ent);
mission = gi.TagMalloc(sizeof(*mission), TAG_LEVEL);
mission->first = true;
mission->obj = 1;
menu = PMenu_Open(ent, mission_menu, -1, sizeof(mission_menu) / sizeof(pmenu_t), mission);
ASLTMissionUpdate(ent, menu);
}
//CW--