mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-30 07:51:40 +00:00
1886 lines
47 KiB
C
1886 lines
47 KiB
C
// Copyright (C) 2000-2002 Raven Software, Inc.
|
|
//
|
|
/*****************************************************************************
|
|
* name: g_saga.c
|
|
*
|
|
* desc: Game-side module for Siege gametype.
|
|
*
|
|
* $Author: Rich Whitehouse $
|
|
* $Revision: 1.6 $
|
|
*
|
|
*****************************************************************************/
|
|
#include "g_local.h"
|
|
#include "bg_saga.h"
|
|
|
|
#define SIEGEITEM_STARTOFFRADAR 8
|
|
|
|
static char team1[512];
|
|
static char team2[512];
|
|
|
|
siegePers_t g_siegePersistant = {qfalse, 0, 0};
|
|
|
|
int imperial_goals_required = 0;
|
|
int imperial_goals_completed = 0;
|
|
int rebel_goals_required = 0;
|
|
int rebel_goals_completed = 0;
|
|
|
|
int imperial_time_limit = 0;
|
|
int rebel_time_limit = 0;
|
|
|
|
int gImperialCountdown = 0;
|
|
int gRebelCountdown = 0;
|
|
|
|
int rebel_attackers = 0;
|
|
int imperial_attackers = 0;
|
|
|
|
qboolean gSiegeRoundBegun = qfalse;
|
|
qboolean gSiegeRoundEnded = qfalse;
|
|
qboolean gSiegeRoundWinningTeam = 0;
|
|
int gSiegeBeginTime = Q3_INFINITE;
|
|
|
|
int g_preroundState = 0; //default to starting as spec (1 is starting ingame)
|
|
|
|
void LogExit( const char *string );
|
|
void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin);
|
|
|
|
static char gParseObjectives[MAX_SIEGE_INFO_SIZE];
|
|
static char gObjectiveCfgStr[1024];
|
|
|
|
//go through all classes on a team and register their
|
|
//weapons and items for precaching.
|
|
void G_SiegeRegisterWeaponsAndHoldables(int team)
|
|
{
|
|
siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team);
|
|
|
|
if (stm)
|
|
{
|
|
int i = 0;
|
|
siegeClass_t *scl;
|
|
while (i < stm->numClasses)
|
|
{
|
|
scl = stm->classes[i];
|
|
|
|
if (scl)
|
|
{
|
|
int j = 0;
|
|
while (j < WP_NUM_WEAPONS)
|
|
{
|
|
if (scl->weapons & (1 << j))
|
|
{ //we use this weapon so register it.
|
|
RegisterItem(BG_FindItemForWeapon(j));
|
|
}
|
|
j++;
|
|
}
|
|
j = 0;
|
|
while (j < HI_NUM_HOLDABLE)
|
|
{
|
|
if (scl->invenItems & (1 << j))
|
|
{ //we use this item so register it.
|
|
RegisterItem(BG_FindItemForHoldable(j));
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//tell clients that this team won and print it on their scoreboard for intermission
|
|
//or whatever.
|
|
void SiegeSetCompleteData(int team)
|
|
{
|
|
trap_SetConfigstring(CS_SIEGE_WINTEAM, va("%i", team));
|
|
}
|
|
|
|
void InitSiegeMode(void)
|
|
{
|
|
vmCvar_t mapname;
|
|
char levelname[512];
|
|
char teamIcon[128];
|
|
char goalreq[64];
|
|
char teams[2048];
|
|
char objective[MAX_SIEGE_INFO_SIZE];
|
|
char objecStr[8192];
|
|
int len = 0;
|
|
int i = 0;
|
|
// int j = 0;
|
|
int objectiveNumTeam1 = 0;
|
|
int objectiveNumTeam2 = 0;
|
|
fileHandle_t f;
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{
|
|
goto failure;
|
|
}
|
|
|
|
//reset
|
|
SiegeSetCompleteData(0);
|
|
|
|
//get pers data in case it existed from last level
|
|
if (g_siegeTeamSwitch.integer)
|
|
{
|
|
trap_SiegePersGet(&g_siegePersistant);
|
|
if (g_siegePersistant.beatingTime)
|
|
{
|
|
trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, va("%i", g_siegePersistant.lastTime));
|
|
}
|
|
else
|
|
{
|
|
trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, "0");
|
|
}
|
|
}
|
|
else
|
|
{ //hmm, ok, nothing.
|
|
trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, "0");
|
|
}
|
|
|
|
imperial_goals_completed = 0;
|
|
rebel_goals_completed = 0;
|
|
|
|
trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
|
|
|
|
Com_sprintf(levelname, sizeof(levelname), "maps/%s.siege\0", mapname.string);
|
|
|
|
if (!levelname || !levelname[0])
|
|
{
|
|
goto failure;
|
|
}
|
|
|
|
len = trap_FS_FOpenFile(levelname, &f, FS_READ);
|
|
|
|
if (!f || len >= MAX_SIEGE_INFO_SIZE)
|
|
{
|
|
goto failure;
|
|
}
|
|
|
|
trap_FS_Read(siege_info, len, f);
|
|
|
|
trap_FS_FCloseFile(f);
|
|
|
|
siege_valid = 1;
|
|
|
|
//See if players should be specs or ingame preround
|
|
if (BG_SiegeGetPairedValue(siege_info, "preround_state", teams))
|
|
{
|
|
if (teams[0])
|
|
{
|
|
g_preroundState = atoi(teams);
|
|
}
|
|
}
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, "Teams", teams))
|
|
{
|
|
if (g_siegeTeam1.string[0] && Q_stricmp(g_siegeTeam1.string, "none"))
|
|
{ //check for override
|
|
strcpy(team1, g_siegeTeam1.string);
|
|
}
|
|
else
|
|
{ //otherwise use level default
|
|
BG_SiegeGetPairedValue(teams, "team1", team1);
|
|
}
|
|
|
|
if (g_siegeTeam2.string[0] && Q_stricmp(g_siegeTeam2.string, "none"))
|
|
{ //check for override
|
|
strcpy(team2, g_siegeTeam2.string);
|
|
}
|
|
else
|
|
{ //otherwise use level default
|
|
BG_SiegeGetPairedValue(teams, "team2", team2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_Error("Siege teams not defined");
|
|
}
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, team2, gParseObjectives))
|
|
{
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "TeamIcon", teamIcon))
|
|
{
|
|
trap_Cvar_Set( "team2_icon", teamIcon);
|
|
}
|
|
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "RequiredObjectives", goalreq))
|
|
{
|
|
rebel_goals_required = atoi(goalreq);
|
|
}
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "Timed", goalreq))
|
|
{
|
|
rebel_time_limit = atoi(goalreq)*1000;
|
|
if (g_siegeTeamSwitch.integer &&
|
|
g_siegePersistant.beatingTime)
|
|
{
|
|
gRebelCountdown = level.time + g_siegePersistant.lastTime;
|
|
}
|
|
else
|
|
{
|
|
gRebelCountdown = level.time + rebel_time_limit;
|
|
}
|
|
}
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "attackers", goalreq))
|
|
{
|
|
rebel_attackers = atoi(goalreq);
|
|
}
|
|
}
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, team1, gParseObjectives))
|
|
{
|
|
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "TeamIcon", teamIcon))
|
|
{
|
|
trap_Cvar_Set( "team1_icon", teamIcon);
|
|
}
|
|
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "RequiredObjectives", goalreq))
|
|
{
|
|
imperial_goals_required = atoi(goalreq);
|
|
}
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "Timed", goalreq))
|
|
{
|
|
if (rebel_time_limit)
|
|
{
|
|
Com_Printf("Tried to set imperial time limit, but there's already a rebel time limit!\nOnly one team can have a time limit.\n");
|
|
}
|
|
else
|
|
{
|
|
imperial_time_limit = atoi(goalreq)*1000;
|
|
if (g_siegeTeamSwitch.integer &&
|
|
g_siegePersistant.beatingTime)
|
|
{
|
|
gImperialCountdown = level.time + g_siegePersistant.lastTime;
|
|
}
|
|
else
|
|
{
|
|
gImperialCountdown = level.time + imperial_time_limit;
|
|
}
|
|
}
|
|
}
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "attackers", goalreq))
|
|
{
|
|
imperial_attackers = atoi(goalreq);
|
|
}
|
|
}
|
|
|
|
//Load the player class types
|
|
BG_SiegeLoadClasses(NULL);
|
|
|
|
if (!bgNumSiegeClasses)
|
|
{ //We didn't find any?!
|
|
G_Error("Couldn't find any player classes for Siege");
|
|
}
|
|
|
|
/*
|
|
//We could probably just see what teams are used on this level,
|
|
//then see what classes are used by those teams, and then precache
|
|
//all weapons for said classes. However, I'm just going to do them
|
|
//all for now.
|
|
while (i < bgNumSiegeClasses)
|
|
{
|
|
cl = &bgSiegeClasses[i];
|
|
j = 0;
|
|
|
|
while (j < WP_NUM_WEAPONS)
|
|
{
|
|
if (cl->weapons & (1 << j))
|
|
{ //we use this weapon so register it.
|
|
RegisterItem(BG_FindItemForWeapon(j));
|
|
}
|
|
|
|
j++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
*/
|
|
//Ok, I'm adding inventory item precaching now, so I'm finally going to optimize this
|
|
//to only do weapons/items for the current teams used on the level.
|
|
|
|
//Now load the teams since we have class data.
|
|
BG_SiegeLoadTeams();
|
|
|
|
if (!bgNumSiegeTeams)
|
|
{ //React same as with classes.
|
|
G_Error("Couldn't find any player teams for Siege");
|
|
}
|
|
|
|
//Get and set the team themes for each team. This will control which classes can be
|
|
//used on each team.
|
|
if (BG_SiegeGetValueGroup(siege_info, team1, gParseObjectives))
|
|
{
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "UseTeam", goalreq))
|
|
{
|
|
BG_SiegeSetTeamTheme(SIEGETEAM_TEAM1, goalreq);
|
|
}
|
|
|
|
//Now count up the objectives for this team.
|
|
i = 1;
|
|
strcpy(objecStr, va("Objective%i", i));
|
|
while (BG_SiegeGetValueGroup(gParseObjectives, objecStr, objective))
|
|
{
|
|
objectiveNumTeam1++;
|
|
i++;
|
|
strcpy(objecStr, va("Objective%i", i));
|
|
}
|
|
}
|
|
if (BG_SiegeGetValueGroup(siege_info, team2, gParseObjectives))
|
|
{
|
|
if (BG_SiegeGetPairedValue(gParseObjectives, "UseTeam", goalreq))
|
|
{
|
|
BG_SiegeSetTeamTheme(SIEGETEAM_TEAM2, goalreq);
|
|
}
|
|
|
|
//Now count up the objectives for this team.
|
|
i = 1;
|
|
strcpy(objecStr, va("Objective%i", i));
|
|
while (BG_SiegeGetValueGroup(gParseObjectives, objecStr, objective))
|
|
{
|
|
objectiveNumTeam2++;
|
|
i++;
|
|
strcpy(objecStr, va("Objective%i", i));
|
|
}
|
|
}
|
|
|
|
//Set the configstring to show status of all current objectives
|
|
strcpy(gObjectiveCfgStr, "t1");
|
|
while (objectiveNumTeam1 > 0)
|
|
{ //mark them all as not completed since we just initialized
|
|
Q_strcat(gObjectiveCfgStr, 1024, "-0");
|
|
objectiveNumTeam1--;
|
|
}
|
|
//Finished doing team 1's objectives, now do team 2's
|
|
Q_strcat(gObjectiveCfgStr, 1024, "|t2");
|
|
while (objectiveNumTeam2 > 0)
|
|
{
|
|
Q_strcat(gObjectiveCfgStr, 1024, "-0");
|
|
objectiveNumTeam2--;
|
|
}
|
|
|
|
//And finally set the actual config string
|
|
trap_SetConfigstring(CS_SIEGE_OBJECTIVES, gObjectiveCfgStr);
|
|
|
|
//precache saber data for classes that use sabers on both teams
|
|
BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM1);
|
|
BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM2);
|
|
|
|
G_SiegeRegisterWeaponsAndHoldables(SIEGETEAM_TEAM1);
|
|
G_SiegeRegisterWeaponsAndHoldables(SIEGETEAM_TEAM2);
|
|
|
|
return;
|
|
|
|
failure:
|
|
siege_valid = 0;
|
|
}
|
|
|
|
void G_SiegeSetObjectiveComplete(int team, int objective, qboolean failIt)
|
|
{
|
|
char *p = NULL;
|
|
int onObjective = 0;
|
|
|
|
if (team == SIEGETEAM_TEAM1)
|
|
{
|
|
p = strstr(gObjectiveCfgStr, "t1");
|
|
}
|
|
else if (team == SIEGETEAM_TEAM2)
|
|
{
|
|
p = strstr(gObjectiveCfgStr, "t2");
|
|
}
|
|
|
|
if (!p)
|
|
{
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
//Parse from the beginning of this team's objectives until we get to the desired objective
|
|
//number.
|
|
while (p && *p && *p != '|')
|
|
{
|
|
if (*p == '-')
|
|
{
|
|
onObjective++;
|
|
}
|
|
|
|
if (onObjective == objective)
|
|
{ //this is the one we want
|
|
//Move to the next char, the status of this objective
|
|
p++;
|
|
|
|
//Now change it from '0' to '1' if we are completeing the objective
|
|
//or vice versa if the objective has been taken away
|
|
if (failIt)
|
|
{
|
|
*p = '0';
|
|
}
|
|
else
|
|
{
|
|
*p = '1';
|
|
}
|
|
break;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
//Now re-update the configstring.
|
|
trap_SetConfigstring(CS_SIEGE_OBJECTIVES, gObjectiveCfgStr);
|
|
}
|
|
|
|
//Returns qtrue if objective complete currently, otherwise qfalse
|
|
qboolean G_SiegeGetCompletionStatus(int team, int objective)
|
|
{
|
|
char *p = NULL;
|
|
int onObjective = 0;
|
|
|
|
if (team == SIEGETEAM_TEAM1)
|
|
{
|
|
p = strstr(gObjectiveCfgStr, "t1");
|
|
}
|
|
else if (team == SIEGETEAM_TEAM2)
|
|
{
|
|
p = strstr(gObjectiveCfgStr, "t2");
|
|
}
|
|
|
|
if (!p)
|
|
{
|
|
assert(0);
|
|
return qfalse;
|
|
}
|
|
|
|
//Parse from the beginning of this team's objectives until we get to the desired objective
|
|
//number.
|
|
while (p && *p && *p != '|')
|
|
{
|
|
if (*p == '-')
|
|
{
|
|
onObjective++;
|
|
}
|
|
|
|
if (onObjective == objective)
|
|
{ //this is the one we want
|
|
//Move to the next char, the status of this objective
|
|
p++;
|
|
|
|
//return qtrue if it's '1', qfalse if it's anything else
|
|
if (*p == '1')
|
|
{
|
|
return qtrue;
|
|
}
|
|
else
|
|
{
|
|
return qfalse;
|
|
}
|
|
break;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
void UseSiegeTarget(gentity_t *other, gentity_t *en, char *target)
|
|
{ //actually use the player which triggered the object which triggered the siege objective to trigger the target
|
|
gentity_t *t;
|
|
gentity_t *ent;
|
|
|
|
if ( !en || !en->client )
|
|
{ //looks like we don't have access to a player, so just use the activating entity
|
|
ent = other;
|
|
}
|
|
else
|
|
{
|
|
ent = en;
|
|
}
|
|
|
|
if (!en)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !target )
|
|
{
|
|
return;
|
|
}
|
|
|
|
t = NULL;
|
|
while ( (t = G_Find (t, FOFS(targetname), target)) != NULL )
|
|
{
|
|
if ( t == ent )
|
|
{
|
|
G_Printf ("WARNING: Entity used itself.\n");
|
|
}
|
|
else
|
|
{
|
|
if ( t->use )
|
|
{
|
|
GlobalUse(t, ent, ent);
|
|
}
|
|
}
|
|
if ( !ent->inuse )
|
|
{
|
|
G_Printf("entity was removed while using targets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SiegeBroadcast_OBJECTIVECOMPLETE(int team, int client, int objective)
|
|
{
|
|
gentity_t *te;
|
|
vec3_t nomatter;
|
|
|
|
VectorClear(nomatter);
|
|
|
|
te = G_TempEntity( nomatter, EV_SIEGE_OBJECTIVECOMPLETE );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = team;
|
|
te->s.weapon = client;
|
|
te->s.trickedentindex = objective;
|
|
}
|
|
|
|
void SiegeBroadcast_ROUNDOVER(int winningteam, int winningclient)
|
|
{
|
|
gentity_t *te;
|
|
vec3_t nomatter;
|
|
|
|
VectorClear(nomatter);
|
|
|
|
te = G_TempEntity( nomatter, EV_SIEGE_ROUNDOVER );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = winningteam;
|
|
te->s.weapon = winningclient;
|
|
}
|
|
|
|
void BroadcastObjectiveCompletion(int team, int objective, int final, int client)
|
|
{
|
|
if (client != ENTITYNUM_NONE && g_entities[client].client && g_entities[client].client->sess.sessionTeam == team)
|
|
{ //guy who completed this objective gets points, providing he's on the opposing team
|
|
AddScore(&g_entities[client], g_entities[client].client->ps.origin, SIEGE_POINTS_OBJECTIVECOMPLETED);
|
|
}
|
|
|
|
SiegeBroadcast_OBJECTIVECOMPLETE(team, client, objective);
|
|
//G_Printf("Broadcast goal completion team %i objective %i final %i\n", team, objective, final);
|
|
}
|
|
|
|
void AddSiegeWinningTeamPoints(int team, int winner)
|
|
{
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->client && ent->client->sess.sessionTeam == team)
|
|
{
|
|
if (i == winner)
|
|
{
|
|
AddScore(ent, ent->client->ps.origin, SIEGE_POINTS_TEAMWONROUND+SIEGE_POINTS_FINALOBJECTIVECOMPLETED);
|
|
}
|
|
else
|
|
{
|
|
AddScore(ent, ent->client->ps.origin, SIEGE_POINTS_TEAMWONROUND);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void SiegeClearSwitchData(void)
|
|
{
|
|
memset(&g_siegePersistant, 0, sizeof(g_siegePersistant));
|
|
trap_SiegePersSet(&g_siegePersistant);
|
|
}
|
|
|
|
void SiegeDoTeamAssign(void)
|
|
{
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
|
|
//yeah, this is great...
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent->inuse && ent->client &&
|
|
ent->client->pers.connected == CON_CONNECTED)
|
|
{ //a connected client, switch his frickin teams around
|
|
if (ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM1)
|
|
{
|
|
ent->client->sess.siegeDesiredTeam = SIEGETEAM_TEAM2;
|
|
}
|
|
else if (ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM2)
|
|
{
|
|
ent->client->sess.siegeDesiredTeam = SIEGETEAM_TEAM1;
|
|
}
|
|
|
|
if (ent->client->sess.sessionTeam == SIEGETEAM_TEAM1)
|
|
{
|
|
SetTeamQuick(ent, SIEGETEAM_TEAM2, qfalse);
|
|
}
|
|
else if (ent->client->sess.sessionTeam == SIEGETEAM_TEAM2)
|
|
{
|
|
SetTeamQuick(ent, SIEGETEAM_TEAM1, qfalse);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void SiegeTeamSwitch(int winTeam, int winTime)
|
|
{
|
|
trap_SiegePersGet(&g_siegePersistant);
|
|
if (g_siegePersistant.beatingTime)
|
|
{ //was already in "switched" mode, change back
|
|
//announce the winning team.
|
|
//either the first team won again, or the second
|
|
//team beat the time set by the initial team. In any
|
|
//case the winTeam here is the overall winning team.
|
|
SiegeSetCompleteData(winTeam);
|
|
SiegeClearSwitchData();
|
|
}
|
|
else
|
|
{ //go into "beat their time" mode
|
|
g_siegePersistant.beatingTime = qtrue;
|
|
g_siegePersistant.lastTeam = winTeam;
|
|
g_siegePersistant.lastTime = winTime;
|
|
|
|
trap_SiegePersSet(&g_siegePersistant);
|
|
}
|
|
}
|
|
|
|
void SiegeRoundComplete(int winningteam, int winningclient)
|
|
{
|
|
vec3_t nomatter;
|
|
char teamstr[1024];
|
|
int originalWinningClient = winningclient;
|
|
|
|
//G_Printf("Team %i won\n", winningteam);
|
|
|
|
if (winningclient != ENTITYNUM_NONE && g_entities[winningclient].client &&
|
|
g_entities[winningclient].client->sess.sessionTeam != winningteam)
|
|
{ //this person just won the round for the other team..
|
|
winningclient = ENTITYNUM_NONE;
|
|
}
|
|
|
|
VectorClear(nomatter);
|
|
|
|
SiegeBroadcast_ROUNDOVER(winningteam, winningclient);
|
|
|
|
AddSiegeWinningTeamPoints(winningteam, winningclient);
|
|
|
|
//Instead of exiting like this, fire off a target, and let it handle things.
|
|
//Can be a script or whatever the designer wants.
|
|
if (winningteam == SIEGETEAM_TEAM1)
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team1);
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team2);
|
|
}
|
|
|
|
trap_SetConfigstring(CS_SIEGE_STATE, va("3|%i", level.time)); //ended
|
|
gSiegeRoundBegun = qfalse;
|
|
gSiegeRoundEnded = qtrue;
|
|
gSiegeRoundWinningTeam = winningteam;
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
|
|
{
|
|
if (!BG_SiegeGetPairedValue(gParseObjectives, "roundover_target", teamstr))
|
|
{ //didn't find the name of the thing to target upon win, just logexit now then.
|
|
LogExit( "Objectives completed" );
|
|
return;
|
|
}
|
|
|
|
if (originalWinningClient == ENTITYNUM_NONE)
|
|
{ //oh well, just find something active and use it then.
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent->inuse)
|
|
{ //sure, you'll do.
|
|
originalWinningClient = ent->s.number;
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
G_UseTargets2(&g_entities[originalWinningClient], &g_entities[originalWinningClient], teamstr);
|
|
}
|
|
|
|
if (g_siegeTeamSwitch.integer &&
|
|
(imperial_time_limit || rebel_time_limit))
|
|
{ //handle stupid team switching crap
|
|
int time = 0;
|
|
if (imperial_time_limit)
|
|
{
|
|
time = imperial_time_limit-(gImperialCountdown-level.time);
|
|
}
|
|
else if (rebel_time_limit)
|
|
{
|
|
time = rebel_time_limit-(gRebelCountdown-level.time);
|
|
}
|
|
|
|
if (time < 1)
|
|
{
|
|
time = 1;
|
|
}
|
|
SiegeTeamSwitch(winningteam, time);
|
|
}
|
|
else
|
|
{ //assure it's clear for next round
|
|
SiegeClearSwitchData();
|
|
}
|
|
}
|
|
|
|
void G_ValidateSiegeClassForTeam(gentity_t *ent, int team)
|
|
{
|
|
siegeClass_t *scl;
|
|
siegeTeam_t *stm;
|
|
int newClassIndex = -1;
|
|
if (ent->client->siegeClass == -1)
|
|
{ //uh.. sure.
|
|
return;
|
|
}
|
|
|
|
scl = &bgSiegeClasses[ent->client->siegeClass];
|
|
|
|
stm = BG_SiegeFindThemeForTeam(team);
|
|
if (stm)
|
|
{
|
|
int i = 0;
|
|
|
|
while (i < stm->numClasses)
|
|
{ //go through the team and see its valid classes, can we find one that matches our current player class?
|
|
if (stm->classes[i])
|
|
{
|
|
if (!Q_stricmp(scl->name, stm->classes[i]->name))
|
|
{ //the class we're using is already ok for this team.
|
|
return;
|
|
}
|
|
if (stm->classes[i]->playerClass == scl->playerClass ||
|
|
newClassIndex == -1)
|
|
{
|
|
newClassIndex = i;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (newClassIndex != -1)
|
|
{ //ok, let's find it in the global class array
|
|
ent->client->siegeClass = BG_SiegeFindClassIndexByName(stm->classes[newClassIndex]->name);
|
|
strcpy(ent->client->sess.siegeClass, stm->classes[newClassIndex]->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
//bypass most of the normal checks in SetTeam
|
|
void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin)
|
|
{
|
|
char userinfo[MAX_INFO_STRING];
|
|
|
|
trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) );
|
|
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{
|
|
G_ValidateSiegeClassForTeam(ent, team);
|
|
}
|
|
|
|
ent->client->sess.sessionTeam = team;
|
|
|
|
if (team == TEAM_SPECTATOR)
|
|
{
|
|
ent->client->sess.spectatorState = SPECTATOR_FREE;
|
|
Info_SetValueForKey(userinfo, "team", "s");
|
|
}
|
|
else
|
|
{
|
|
ent->client->sess.spectatorState = SPECTATOR_NOT;
|
|
if (team == TEAM_RED)
|
|
{
|
|
Info_SetValueForKey(userinfo, "team", "r");
|
|
}
|
|
else if (team == TEAM_BLUE)
|
|
{
|
|
Info_SetValueForKey(userinfo, "team", "b");
|
|
}
|
|
else
|
|
{
|
|
Info_SetValueForKey(userinfo, "team", "?");
|
|
}
|
|
}
|
|
|
|
trap_SetUserinfo( ent->s.number, userinfo );
|
|
|
|
ent->client->sess.spectatorClient = 0;
|
|
|
|
ent->client->pers.teamState.state = TEAM_BEGIN;
|
|
|
|
ClientUserinfoChanged( ent->s.number );
|
|
|
|
if (doBegin)
|
|
{
|
|
ClientBegin( ent->s.number, qfalse );
|
|
}
|
|
}
|
|
|
|
void SiegeRespawn(gentity_t *ent)
|
|
{
|
|
gentity_t *tent;
|
|
|
|
if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam)
|
|
{
|
|
SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qtrue);
|
|
}
|
|
else
|
|
{
|
|
ClientSpawn(ent);
|
|
// add a teleportation effect
|
|
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
}
|
|
}
|
|
|
|
void SiegeBeginRound(int entNum)
|
|
{ //entNum is just used as something to fire targets from.
|
|
char targname[1024];
|
|
|
|
if (!g_preroundState)
|
|
{ //if players are not ingame on round start then respawn them now
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
qboolean spawnEnt = qfalse;
|
|
|
|
//respawn everyone now
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent->inuse && ent->client)
|
|
{
|
|
if (ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
|
|
!(ent->client->ps.pm_flags & PMF_FOLLOW))
|
|
{ //not a spec, just respawn them
|
|
spawnEnt = qtrue;
|
|
}
|
|
else if (ent->client->sess.sessionTeam == TEAM_SPECTATOR &&
|
|
(ent->client->sess.siegeDesiredTeam == TEAM_RED ||
|
|
ent->client->sess.siegeDesiredTeam == TEAM_BLUE))
|
|
{ //spectator but has a desired team
|
|
spawnEnt = qtrue;
|
|
}
|
|
}
|
|
|
|
if (spawnEnt)
|
|
{
|
|
SiegeRespawn(ent);
|
|
|
|
spawnEnt = qfalse;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
//Now check if there's something to fire off at the round start, if so do it.
|
|
if (BG_SiegeGetPairedValue(siege_info, "roundbegin_target", targname))
|
|
{
|
|
if (targname[0])
|
|
{
|
|
G_UseTargets2(&g_entities[entNum], &g_entities[entNum], targname);
|
|
}
|
|
}
|
|
|
|
trap_SetConfigstring(CS_SIEGE_STATE, va("0|%i", level.time)); //we're ready to g0g0g0
|
|
}
|
|
|
|
void SiegeCheckTimers(void)
|
|
{
|
|
int i=0;
|
|
gentity_t *ent;
|
|
int numTeam1 = 0;
|
|
int numTeam2 = 0;
|
|
|
|
if (g_gametype.integer != GT_SIEGE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (level.intermissiontime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gSiegeRoundEnded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!gSiegeRoundBegun)
|
|
{ //check if anyone is active on this team - if not, keep the timer set up.
|
|
i = 0;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->inuse && ent->client &&
|
|
ent->client->pers.connected == CON_CONNECTED &&
|
|
ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM1)
|
|
{
|
|
numTeam1++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->inuse && ent->client &&
|
|
ent->client->pers.connected == CON_CONNECTED &&
|
|
ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM2)
|
|
{
|
|
numTeam2++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (g_siegeTeamSwitch.integer &&
|
|
g_siegePersistant.beatingTime)
|
|
{
|
|
gImperialCountdown = level.time + g_siegePersistant.lastTime;
|
|
gRebelCountdown = level.time + g_siegePersistant.lastTime;
|
|
}
|
|
else
|
|
{
|
|
gImperialCountdown = level.time + imperial_time_limit;
|
|
gRebelCountdown = level.time + rebel_time_limit;
|
|
}
|
|
}
|
|
|
|
if (imperial_time_limit)
|
|
{ //team1
|
|
if (gImperialCountdown < level.time)
|
|
{
|
|
SiegeRoundComplete(SIEGETEAM_TEAM2, ENTITYNUM_NONE);
|
|
imperial_time_limit = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (rebel_time_limit)
|
|
{ //team2
|
|
if (gRebelCountdown < level.time)
|
|
{
|
|
SiegeRoundComplete(SIEGETEAM_TEAM1, ENTITYNUM_NONE);
|
|
rebel_time_limit = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!gSiegeRoundBegun)
|
|
{
|
|
if (!numTeam1 || !numTeam2)
|
|
{ //don't have people on both teams yet.
|
|
gSiegeBeginTime = level.time + SIEGE_ROUND_BEGIN_TIME;
|
|
trap_SetConfigstring(CS_SIEGE_STATE, "1"); //"waiting for players on both teams"
|
|
}
|
|
else if (gSiegeBeginTime < level.time)
|
|
{ //mark the round as having begun
|
|
gSiegeRoundBegun = qtrue;
|
|
SiegeBeginRound(i); //perform any round start tasks
|
|
}
|
|
else if (gSiegeBeginTime > (level.time + SIEGE_ROUND_BEGIN_TIME))
|
|
{
|
|
gSiegeBeginTime = level.time + SIEGE_ROUND_BEGIN_TIME;
|
|
}
|
|
else
|
|
{
|
|
trap_SetConfigstring(CS_SIEGE_STATE, va("2|%i", gSiegeBeginTime - SIEGE_ROUND_BEGIN_TIME)); //getting ready to begin
|
|
}
|
|
}
|
|
}
|
|
|
|
void SiegeObjectiveCompleted(int team, int objective, int final, int client)
|
|
{
|
|
int goals_completed, goals_required;
|
|
|
|
if (gSiegeRoundEnded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Update the configstring status
|
|
G_SiegeSetObjectiveComplete(team, objective, qfalse);
|
|
|
|
if (final != -1)
|
|
{
|
|
if (team == SIEGETEAM_TEAM1)
|
|
{
|
|
imperial_goals_completed++;
|
|
}
|
|
else
|
|
{
|
|
rebel_goals_completed++;
|
|
}
|
|
}
|
|
|
|
if (team == SIEGETEAM_TEAM1)
|
|
{
|
|
goals_completed = imperial_goals_completed;
|
|
goals_required = imperial_goals_required;
|
|
}
|
|
else
|
|
{
|
|
goals_completed = rebel_goals_completed;
|
|
goals_required = rebel_goals_required;
|
|
}
|
|
|
|
if (final == 1 || goals_completed >= goals_required)
|
|
{
|
|
SiegeRoundComplete(team, client);
|
|
}
|
|
else
|
|
{
|
|
BroadcastObjectiveCompletion(team, objective, final, client);
|
|
}
|
|
}
|
|
|
|
void siegeTriggerUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
|
|
{
|
|
char teamstr[64];
|
|
char objectivestr[64];
|
|
char desiredobjective[MAX_SIEGE_INFO_SIZE];
|
|
int clUser = ENTITYNUM_NONE;
|
|
int final = 0;
|
|
int i = 0;
|
|
|
|
if (!siege_valid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!(ent->s.eFlags & EF_RADAROBJECT))
|
|
{ //toggle radar on and exit if it is not showing up already
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
return;
|
|
}
|
|
|
|
if (activator && activator->client)
|
|
{ //activator will hopefully be the person who triggered this event
|
|
clUser = activator->s.number;
|
|
}
|
|
|
|
if (ent->side == SIEGETEAM_TEAM1)
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team1);
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team2);
|
|
}
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
|
|
{
|
|
Com_sprintf(objectivestr, sizeof(objectivestr), "Objective%i", ent->objective);
|
|
|
|
if (BG_SiegeGetValueGroup(gParseObjectives, objectivestr, desiredobjective))
|
|
{
|
|
if (BG_SiegeGetPairedValue(desiredobjective, "final", teamstr))
|
|
{
|
|
final = atoi(teamstr);
|
|
}
|
|
|
|
if (BG_SiegeGetPairedValue(desiredobjective, "target", teamstr))
|
|
{
|
|
while (teamstr[i])
|
|
{
|
|
if (teamstr[i] == '\r' ||
|
|
teamstr[i] == '\n')
|
|
{
|
|
teamstr[i] = '\0';
|
|
}
|
|
|
|
i++;
|
|
}
|
|
UseSiegeTarget(other, activator, teamstr);
|
|
}
|
|
|
|
if (ent->target && ent->target[0])
|
|
{ //use this too
|
|
UseSiegeTarget(other, activator, ent->target);
|
|
}
|
|
|
|
SiegeObjectiveCompleted(ent->side, ent->objective, final, clUser);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_siege_objective (1 0 1) (-16 -16 -24) (16 16 32) ? x x STARTOFFRADAR
|
|
STARTOFFRADAR - start not displaying on radar, don't display until used.
|
|
|
|
"objective" - specifies the objective to complete upon activation
|
|
"side" - set to 1 to specify an imperial goal, 2 to specify rebels
|
|
"icon" - icon that represents the objective on the radar
|
|
*/
|
|
void SP_info_siege_objective (gentity_t *ent)
|
|
{
|
|
char* s;
|
|
|
|
if (!siege_valid || g_gametype.integer != GT_SIEGE)
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
ent->use = siegeTriggerUse;
|
|
G_SpawnInt( "objective", "0", &ent->objective);
|
|
G_SpawnInt( "side", "0", &ent->side);
|
|
|
|
if (!ent->objective || !ent->side)
|
|
{ //j00 fux0red something up
|
|
G_FreeEntity(ent);
|
|
G_Printf("ERROR: info_siege_objective without an objective or side value\n");
|
|
return;
|
|
}
|
|
|
|
//Set it up to be drawn on radar
|
|
if (!(ent->spawnflags & SIEGEITEM_STARTOFFRADAR))
|
|
{
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
}
|
|
|
|
//All clients want to know where it is at all times for radar
|
|
ent->r.svFlags |= SVF_BROADCAST;
|
|
|
|
G_SpawnString( "icon", "", &s );
|
|
|
|
if (s && s[0])
|
|
{
|
|
// We have an icon, so index it now. We are reusing the genericenemyindex
|
|
// variable rather than adding a new one to the entity state.
|
|
ent->s.genericenemyindex = G_IconIndex(s);
|
|
}
|
|
|
|
ent->s.brokenLimbs = ent->side;
|
|
ent->s.frame = ent->objective;
|
|
trap_LinkEntity(ent);
|
|
}
|
|
|
|
|
|
void SiegeIconUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
|
|
{ //toggle it on and off
|
|
if (ent->s.eFlags & EF_RADAROBJECT)
|
|
{
|
|
ent->s.eFlags &= ~EF_RADAROBJECT;
|
|
ent->r.svFlags &= ~SVF_BROADCAST;
|
|
}
|
|
else
|
|
{
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
ent->r.svFlags |= SVF_BROADCAST;
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_siege_radaricon (1 0 1) (-16 -16 -24) (16 16 32) ?
|
|
Used to arbitrarily display radar icons at placed location. Can be used
|
|
to toggle on and off.
|
|
|
|
"icon" - icon that represents the objective on the radar
|
|
"startoff" - if 1 start off
|
|
*/
|
|
void SP_info_siege_radaricon (gentity_t *ent)
|
|
{
|
|
char* s;
|
|
int i;
|
|
|
|
if (!siege_valid || g_gametype.integer != GT_SIEGE)
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
G_SpawnInt("startoff", "0", &i);
|
|
|
|
if (!i)
|
|
{ //start on then
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
ent->r.svFlags |= SVF_BROADCAST;
|
|
}
|
|
|
|
G_SpawnString( "icon", "", &s );
|
|
if (!s || !s[0])
|
|
{ //that's the whole point of the entity
|
|
Com_Error(ERR_DROP, "misc_siege_radaricon without an icon");
|
|
return;
|
|
}
|
|
|
|
ent->use = SiegeIconUse;
|
|
|
|
ent->s.genericenemyindex = G_IconIndex(s);
|
|
|
|
trap_LinkEntity(ent);
|
|
}
|
|
|
|
void decompTriggerUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
|
|
{
|
|
int final = 0;
|
|
char teamstr[1024];
|
|
char objectivestr[64];
|
|
char desiredobjective[MAX_SIEGE_INFO_SIZE];
|
|
|
|
if (gSiegeRoundEnded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!G_SiegeGetCompletionStatus(ent->side, ent->objective))
|
|
{ //if it's not complete then there's nothing to do here
|
|
return;
|
|
}
|
|
|
|
//Update the configstring status
|
|
G_SiegeSetObjectiveComplete(ent->side, ent->objective, qtrue);
|
|
|
|
//Find out if this objective counts toward the final objective count
|
|
if (ent->side == SIEGETEAM_TEAM1)
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team1);
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(teamstr, sizeof(teamstr), team2);
|
|
}
|
|
|
|
if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
|
|
{
|
|
Com_sprintf(objectivestr, sizeof(objectivestr), "Objective%i", ent->objective);
|
|
|
|
if (BG_SiegeGetValueGroup(gParseObjectives, objectivestr, desiredobjective))
|
|
{
|
|
if (BG_SiegeGetPairedValue(desiredobjective, "final", teamstr))
|
|
{
|
|
final = atoi(teamstr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Subtract the goal num if applicable
|
|
if (final != -1)
|
|
{
|
|
if (ent->side == SIEGETEAM_TEAM1)
|
|
{
|
|
imperial_goals_completed--;
|
|
}
|
|
else
|
|
{
|
|
rebel_goals_completed--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*QUAKED info_siege_decomplete (1 0 1) (-16 -16 -24) (16 16 32)
|
|
"objective" - specifies the objective to decomplete upon activation
|
|
"side" - set to 1 to specify an imperial (team1) goal, 2 to specify rebels (team2)
|
|
*/
|
|
void SP_info_siege_decomplete (gentity_t *ent)
|
|
{
|
|
if (!siege_valid || g_gametype.integer != GT_SIEGE)
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
ent->use = decompTriggerUse;
|
|
G_SpawnInt( "objective", "0", &ent->objective);
|
|
G_SpawnInt( "side", "0", &ent->side);
|
|
|
|
if (!ent->objective || !ent->side)
|
|
{ //j00 fux0red something up
|
|
G_FreeEntity(ent);
|
|
G_Printf("ERROR: info_siege_objective_decomplete without an objective or side value\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void siegeEndUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
|
|
{
|
|
LogExit("Round ended");
|
|
}
|
|
|
|
/*QUAKED target_siege_end (1 0 1) (-16 -16 -24) (16 16 32)
|
|
Do a logexit for siege when used.
|
|
*/
|
|
void SP_target_siege_end (gentity_t *ent)
|
|
{
|
|
if (!siege_valid || g_gametype.integer != GT_SIEGE)
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
ent->use = siegeEndUse;
|
|
}
|
|
|
|
#define SIEGE_ITEM_RESPAWN_TIME 20000
|
|
|
|
void SiegeItemRemoveOwner(gentity_t *ent, gentity_t *carrier)
|
|
{
|
|
ent->genericValue2 = 0; //Remove picked-up flag
|
|
|
|
ent->genericValue8 = ENTITYNUM_NONE; //Mark entity carrying us as none
|
|
|
|
if (carrier)
|
|
{
|
|
carrier->client->holdingObjectiveItem = 0; //The carrier is no longer carrying us
|
|
carrier->r.svFlags &= ~SVF_BROADCAST;
|
|
}
|
|
}
|
|
|
|
static void SiegeItemRespawnEffect(gentity_t *ent, vec3_t newOrg)
|
|
{
|
|
vec3_t upAng;
|
|
|
|
if (ent->target5 && ent->target5[0])
|
|
{
|
|
G_UseTargets2(ent, ent, ent->target5);
|
|
}
|
|
|
|
if (!ent->genericValue10)
|
|
{ //no respawn effect
|
|
return;
|
|
}
|
|
|
|
VectorSet(upAng, 0, 0, 1);
|
|
|
|
//Play it once on the current origin, and once on the origin we're respawning to.
|
|
G_PlayEffectID(ent->genericValue10, ent->r.currentOrigin, upAng);
|
|
G_PlayEffectID(ent->genericValue10, newOrg, upAng);
|
|
}
|
|
|
|
static void SiegeItemRespawnOnOriginalSpot(gentity_t *ent, gentity_t *carrier)
|
|
{
|
|
SiegeItemRespawnEffect(ent, ent->pos1);
|
|
G_SetOrigin(ent, ent->pos1);
|
|
SiegeItemRemoveOwner(ent, carrier);
|
|
|
|
// Stop the item from flashing on the radar
|
|
ent->s.time2 = 0;
|
|
}
|
|
|
|
void SiegeItemThink(gentity_t *ent)
|
|
{
|
|
gentity_t *carrier = NULL;
|
|
|
|
if (ent->genericValue12)
|
|
{ //recharge health
|
|
if (ent->health > 0 && ent->health < ent->maxHealth && ent->genericValue14 < level.time)
|
|
{
|
|
ent->genericValue14 = level.time + ent->genericValue13;
|
|
ent->health += ent->genericValue12;
|
|
if (ent->health > ent->maxHealth)
|
|
{
|
|
ent->health = ent->maxHealth;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ent->genericValue8 != ENTITYNUM_NONE)
|
|
{ //Just keep sticking it on top of the owner. We need it in the same PVS as him so it will render bolted onto him properly.
|
|
carrier = &g_entities[ent->genericValue8];
|
|
|
|
if (carrier->inuse && carrier->client)
|
|
{
|
|
VectorCopy(carrier->client->ps.origin, ent->r.currentOrigin);
|
|
trap_LinkEntity(ent);
|
|
}
|
|
}
|
|
else if (ent->genericValue1)
|
|
{ //this means we want to run physics on the object
|
|
G_RunExPhys(ent, ent->radius, ent->mass, ent->random, qfalse, NULL, 0);
|
|
}
|
|
|
|
//Bolt us to whoever is carrying us if a client
|
|
if (ent->genericValue8 < MAX_CLIENTS)
|
|
{
|
|
ent->s.boltToPlayer = ent->genericValue8+1;
|
|
}
|
|
else
|
|
{
|
|
ent->s.boltToPlayer = 0;
|
|
}
|
|
|
|
|
|
if (carrier)
|
|
{
|
|
gentity_t *carrier = &g_entities[ent->genericValue8];
|
|
|
|
//This checking can be a bit iffy on the death stuff, but in theory we should always
|
|
//get a think in before the default minimum respawn time is exceeded.
|
|
if (!carrier->inuse || !carrier->client ||
|
|
(carrier->client->sess.sessionTeam != SIEGETEAM_TEAM1 && carrier->client->sess.sessionTeam != SIEGETEAM_TEAM2) ||
|
|
(carrier->client->ps.pm_flags & PMF_FOLLOW))
|
|
{ //respawn on the original spot
|
|
SiegeItemRespawnOnOriginalSpot(ent, NULL);
|
|
}
|
|
else if (carrier->health < 1)
|
|
{ //The carrier died so pop out where he is (unless in nodrop).
|
|
if (ent->target6 && ent->target6[0])
|
|
{
|
|
G_UseTargets2(ent, ent, ent->target6);
|
|
}
|
|
|
|
if ( trap_PointContents(carrier->client->ps.origin, carrier->s.number) & CONTENTS_NODROP )
|
|
{ //In nodrop land, go back to the original spot.
|
|
SiegeItemRespawnOnOriginalSpot(ent, carrier);
|
|
}
|
|
else
|
|
{
|
|
G_SetOrigin(ent, carrier->client->ps.origin);
|
|
ent->epVelocity[0] = Q_irand(-80, 80);
|
|
ent->epVelocity[1] = Q_irand(-80, 80);
|
|
ent->epVelocity[2] = Q_irand(40, 80);
|
|
|
|
//We're in a nonstandard place, so if we go this long without being touched,
|
|
//assume we may not be reachable and respawn on the original spot.
|
|
ent->genericValue9 = level.time + SIEGE_ITEM_RESPAWN_TIME;
|
|
|
|
SiegeItemRemoveOwner(ent, carrier);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ent->genericValue9 && ent->genericValue9 < level.time)
|
|
{ //time to respawn on the original spot then
|
|
SiegeItemRespawnEffect(ent, ent->pos1);
|
|
G_SetOrigin(ent, ent->pos1);
|
|
ent->genericValue9 = 0;
|
|
|
|
// stop flashing on radar
|
|
ent->s.time2 = 0;
|
|
}
|
|
|
|
ent->nextthink = level.time + FRAMETIME/2;
|
|
}
|
|
|
|
void SiegeItemTouch( gentity_t *self, gentity_t *other, trace_t *trace )
|
|
{
|
|
if (!other || !other->inuse ||
|
|
!other->client || other->s.eType == ET_NPC)
|
|
{
|
|
if (trace && trace->startsolid)
|
|
{ //let me out! (ideally this should not happen, but such is life)
|
|
vec3_t escapePos;
|
|
VectorCopy(self->r.currentOrigin, escapePos);
|
|
escapePos[2] += 1.0f;
|
|
|
|
//I hope you weren't stuck in the ceiling.
|
|
G_SetOrigin(self, escapePos);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (other->health < 1)
|
|
{ //dead people can't pick us up.
|
|
return;
|
|
}
|
|
|
|
if (other->client->holdingObjectiveItem)
|
|
{ //this guy's already carrying a siege item
|
|
return;
|
|
}
|
|
|
|
if ( other->client->ps.pm_type == PM_SPECTATOR )
|
|
{//spectators don't pick stuff up
|
|
return;
|
|
}
|
|
|
|
if (self->genericValue2)
|
|
{ //Am I already picked up?
|
|
return;
|
|
}
|
|
|
|
if (self->genericValue6 == other->client->sess.sessionTeam)
|
|
{ //Set to not be touchable by players on this team.
|
|
return;
|
|
}
|
|
|
|
if (!gSiegeRoundBegun)
|
|
{ //can't pick it up if round hasn't started yet
|
|
return;
|
|
}
|
|
|
|
if (self->noise_index)
|
|
{ //play the pickup noise.
|
|
G_Sound(other, CHAN_AUTO, self->noise_index);
|
|
}
|
|
|
|
self->genericValue2 = 1; //Mark it as picked up.
|
|
|
|
other->client->holdingObjectiveItem = self->s.number;
|
|
other->r.svFlags |= SVF_BROADCAST; //broadcast player while he carries this
|
|
self->genericValue8 = other->s.number; //Keep the index so we know who is "carrying" us
|
|
|
|
self->genericValue9 = 0; //So it doesn't think it has to respawn.
|
|
|
|
if (self->target2 && self->target2[0] && (!self->genericValue4 || !self->genericValue5))
|
|
{ //fire the target for pickup, if it's set to fire every time, or set to only fire the first time and the first time has not yet occured.
|
|
G_UseTargets2(self, self, self->target2);
|
|
self->genericValue5 = 1; //mark it as having been picked up
|
|
}
|
|
|
|
// time2 set to -1 will blink the item on the radar indefinately
|
|
self->s.time2 = 0xFFFFFFFF;
|
|
}
|
|
|
|
void SiegeItemPain(gentity_t *self, gentity_t *attacker, int damage)
|
|
{
|
|
// Time 2 is used to pulse the radar icon to show its under attack
|
|
self->s.time2 = level.time;
|
|
}
|
|
|
|
void SiegeItemDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
self->takedamage = qfalse; //don't die more than once
|
|
|
|
if (self->genericValue3)
|
|
{ //An indexed effect to play on death
|
|
vec3_t upAng;
|
|
|
|
VectorSet(upAng, 0, 0, 1);
|
|
G_PlayEffectID(self->genericValue3, self->r.currentOrigin, upAng);
|
|
}
|
|
|
|
self->neverFree = qfalse;
|
|
self->think = G_FreeEntity;
|
|
self->nextthink = level.time;
|
|
|
|
//Fire off the death target if we've got one.
|
|
if (self->target4 && self->target4[0])
|
|
{
|
|
G_UseTargets2(self, self, self->target4);
|
|
}
|
|
}
|
|
|
|
void SiegeItemUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
|
|
{ //once used, become active
|
|
if (ent->spawnflags & SIEGEITEM_STARTOFFRADAR)
|
|
{ //start showing on radar
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
|
|
if (!(ent->s.eFlags & EF_NODRAW))
|
|
{ //we've nothing else to do here
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{ //make sure it's showing up
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
}
|
|
|
|
if (ent->genericValue11 || !ent->takedamage)
|
|
{ //We want to be able to walk into it to pick it up then.
|
|
ent->r.contents = CONTENTS_TRIGGER;
|
|
ent->clipmask = CONTENTS_SOLID|CONTENTS_TERRAIN;
|
|
if (ent->genericValue11)
|
|
{
|
|
ent->touch = SiegeItemTouch;
|
|
}
|
|
}
|
|
else
|
|
{ //Make it solid.
|
|
ent->r.contents = MASK_PLAYERSOLID;
|
|
ent->clipmask = MASK_PLAYERSOLID;
|
|
}
|
|
|
|
ent->think = SiegeItemThink;
|
|
ent->nextthink = level.time + FRAMETIME/2;
|
|
|
|
//take off nodraw
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
|
|
if (ent->paintarget && ent->paintarget[0])
|
|
{ //want to be on this guy's origin now then
|
|
gentity_t *targ = G_Find (NULL, FOFS(targetname), ent->paintarget);
|
|
|
|
if (targ && targ->inuse)
|
|
{
|
|
G_SetOrigin(ent, targ->r.currentOrigin);
|
|
trap_LinkEntity(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*QUAKED misc_siege_item (1 0 1) (-16 -16 -24) (16 16 32) ? x x STARTOFFRADAR
|
|
STARTOFFRADAR - start not displaying on radar, don't display until used.
|
|
|
|
"model" Name of model to use for the object
|
|
"mins" Actual mins of the object. Careful not to place it into a solid,
|
|
as these new mins will not be reflected visually in the editor.
|
|
Default value is "-16 -16 -24".
|
|
"maxs" Same as above for maxs. Default value is "16 16 32".
|
|
"targetname" If it has a targetname, it will only spawn upon being used.
|
|
"target2" Target to fire upon pickup. If none, nothing will happen.
|
|
"pickuponlyonce" If non-0, target2 will only be fired on the first pickup. If the item is
|
|
dropped and picked up again later, the target will not be fired off on
|
|
the sequential pickup. Default value is 1.
|
|
"target3" Target to fire upon delivery of the item to the goal point.
|
|
If none, nothing will happen. (but you should always want something to happen)
|
|
"health" If > 0, object can be damaged and will die once health reaches 0. Default is 0.
|
|
"showhealth" if health > 0, will show a health meter for this item
|
|
"teamowner" Which team owns this item, used only for deciding what color to make health meter
|
|
"target4" Target to fire upon death, if damageable. Default is none.
|
|
"deathfx" Effect to play on death, if damageable. Default is none.
|
|
"canpickup" If non-0, item can be picked up. Otherwise it will just be solid and sit on the
|
|
ground. Default is 1.
|
|
"pickupsound" Sound to play on pickup, if any.
|
|
"goaltarget" Must be the targetname of a trigger_multi/trigger_once. Once a player carrying
|
|
this object is brought inside the specified trigger, then that trigger will be
|
|
allowed to fire. Ideally it will target a siege objective or something like that.
|
|
"usephysics" If non-0, run standard physics on the object. Default is 1.
|
|
"mass" If usephysics, this will be the factored object mass. Default is 0.09.
|
|
"gravity" If usephysics, this will be the factored gravitational pull. Default is 3.0.
|
|
"bounce" If usephysics, this will be the factored bounce amount. Default is 1.3.
|
|
"teamnotouch" If 1 don't let team 1 pickup, if 2 don't let team 2. By default both teams
|
|
can pick this object up and carry it around until death.
|
|
"teamnocomplete" Same values as above, but controls if this object can be taken into the objective
|
|
area by said team.
|
|
"respawnfx" Plays this effect when respawning (e.g. it is left in an unknown area too long
|
|
and goes back to the original spot). If this is not specified there will be
|
|
no effect. (expected format is .efx file)
|
|
"paintarget" plop self on top of this guy's origin when we are used (only applies if the siege
|
|
item has a targetname)
|
|
"noradar" if non-0 this thing will not show up on radar
|
|
|
|
"forcelimit" if non-0, while carrying this item, the carrier's force powers will be crippled.
|
|
"target5" target to fire when respawning.
|
|
"target6" target to fire when dropped by someone carrying this item.
|
|
|
|
"icon" icon that represents the gametype item on the radar
|
|
|
|
health charge things only work with showhealth 1 on siege items that take damage.
|
|
"health_chargeamt" if non-0 will recharge this much health every...
|
|
"health_chargerate" ...this many milliseconds
|
|
*/
|
|
void SP_misc_siege_item (gentity_t *ent)
|
|
{
|
|
int canpickup;
|
|
int noradar;
|
|
char *s;
|
|
|
|
if (!siege_valid || g_gametype.integer != GT_SIEGE)
|
|
{
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
if (!ent->model || !ent->model[0])
|
|
{
|
|
G_Error("You must specify a model for misc_siege_item types.");
|
|
}
|
|
|
|
G_SpawnInt("canpickup", "1", &canpickup);
|
|
G_SpawnInt("usephysics", "1", &ent->genericValue1);
|
|
|
|
if (ent->genericValue1)
|
|
{ //if we're using physics we want lerporigin smoothing
|
|
ent->s.eFlags |= EF_CLIENTSMOOTH;
|
|
}
|
|
|
|
G_SpawnInt("noradar", "0", &noradar);
|
|
//Want it to always show up as a goal object on radar
|
|
if (!noradar && !(ent->spawnflags & SIEGEITEM_STARTOFFRADAR))
|
|
{
|
|
ent->s.eFlags |= EF_RADAROBJECT;
|
|
}
|
|
|
|
//All clients want to know where it is at all times for radar
|
|
ent->r.svFlags |= SVF_BROADCAST;
|
|
|
|
G_SpawnInt("pickuponlyonce", "1", &ent->genericValue4);
|
|
|
|
G_SpawnInt("teamnotouch", "0", &ent->genericValue6);
|
|
G_SpawnInt("teamnocomplete", "0", &ent->genericValue7);
|
|
|
|
//Get default physics values.
|
|
G_SpawnFloat("mass", "0.09", &ent->mass);
|
|
G_SpawnFloat("gravity", "3.0", &ent->radius);
|
|
G_SpawnFloat("bounce", "1.3", &ent->random);
|
|
|
|
G_SpawnString( "pickupsound", "", &s );
|
|
|
|
if (s && s[0])
|
|
{ //We have a pickup sound, so index it now.
|
|
ent->noise_index = G_SoundIndex(s);
|
|
}
|
|
|
|
G_SpawnString( "deathfx", "", &s );
|
|
|
|
if (s && s[0])
|
|
{ //We have a death effect, so index it now.
|
|
ent->genericValue3 = G_EffectIndex(s);
|
|
}
|
|
|
|
G_SpawnString( "respawnfx", "", &s );
|
|
|
|
if (s && s[0])
|
|
{ //We have a respawn effect, so index it now.
|
|
ent->genericValue10 = G_EffectIndex(s);
|
|
}
|
|
|
|
G_SpawnString( "icon", "", &s );
|
|
|
|
if (s && s[0])
|
|
{
|
|
// We have an icon, so index it now. We are reusing the genericenemyindex
|
|
// variable rather than adding a new one to the entity state.
|
|
ent->s.genericenemyindex = G_IconIndex(s);
|
|
}
|
|
|
|
ent->s.modelindex = G_ModelIndex(ent->model);
|
|
|
|
//Is the model a ghoul2 model?
|
|
if (!Q_stricmp(&ent->model[strlen(ent->model) - 4], ".glm"))
|
|
{ //apparently so.
|
|
ent->s.modelGhoul2 = 1;
|
|
}
|
|
|
|
ent->s.eType = ET_GENERAL;
|
|
|
|
//Set the mins/maxs with default values.
|
|
G_SpawnVector("mins", "-16 -16 -24", ent->r.mins);
|
|
G_SpawnVector("maxs", "16 16 32", ent->r.maxs);
|
|
|
|
VectorCopy(ent->s.origin, ent->pos1); //store off the initial origin for respawning
|
|
G_SetOrigin(ent, ent->s.origin);
|
|
|
|
VectorCopy(ent->s.angles, ent->r.currentAngles);
|
|
VectorCopy(ent->s.angles, ent->s.apos.trBase);
|
|
|
|
G_SpawnInt("forcelimit", "0", &ent->genericValue15);
|
|
|
|
if (ent->health > 0)
|
|
{ //If it has health, it can be killed.
|
|
int t;
|
|
|
|
ent->pain = SiegeItemPain;
|
|
ent->die = SiegeItemDie;
|
|
ent->takedamage = qtrue;
|
|
|
|
G_SpawnInt( "showhealth", "0", &t );
|
|
if (t)
|
|
{ //a non-0 maxhealth value will mean we want to show the health on the hud
|
|
ent->maxHealth = ent->health;
|
|
G_ScaleNetHealth(ent);
|
|
|
|
G_SpawnInt( "health_chargeamt", "0", &ent->genericValue12);
|
|
G_SpawnInt( "health_chargerate", "0", &ent->genericValue13);
|
|
}
|
|
}
|
|
else
|
|
{ //Otherwise no.
|
|
ent->takedamage = qfalse;
|
|
}
|
|
|
|
if (ent->spawnflags & SIEGEITEM_STARTOFFRADAR)
|
|
{
|
|
ent->use = SiegeItemUse;
|
|
}
|
|
else if (ent->targetname && ent->targetname[0])
|
|
{
|
|
ent->s.eFlags |= EF_NODRAW; //kind of hacky, but whatever
|
|
ent->genericValue11 = canpickup;
|
|
ent->use = SiegeItemUse;
|
|
ent->s.eFlags &= ~EF_RADAROBJECT;
|
|
}
|
|
|
|
if ( (!ent->targetname || !ent->targetname[0]) ||
|
|
(ent->spawnflags & SIEGEITEM_STARTOFFRADAR) )
|
|
{
|
|
if (canpickup || !ent->takedamage)
|
|
{ //We want to be able to walk into it to pick it up then.
|
|
ent->r.contents = CONTENTS_TRIGGER;
|
|
ent->clipmask = CONTENTS_SOLID|CONTENTS_TERRAIN;
|
|
if (canpickup)
|
|
{
|
|
ent->touch = SiegeItemTouch;
|
|
}
|
|
}
|
|
else
|
|
{ //Make it solid.
|
|
ent->r.contents = MASK_PLAYERSOLID;
|
|
ent->clipmask = MASK_PLAYERSOLID;
|
|
}
|
|
|
|
ent->think = SiegeItemThink;
|
|
ent->nextthink = level.time + FRAMETIME/2;
|
|
}
|
|
|
|
ent->genericValue8 = ENTITYNUM_NONE; //initialize the carrier to none
|
|
|
|
ent->neverFree = qtrue; //never free us unless we specifically request it.
|
|
|
|
trap_LinkEntity(ent);
|
|
}
|
|
|
|
//sends extra data about other client's in this client's PVS
|
|
//used for support guy etc.
|
|
//with this formatting:
|
|
//sxd 16,999,999,999|17,999,999,999
|
|
//assumed max 2 chars for cl num, 3 chars per ammo/health/maxhealth, even a single string full of
|
|
//info for all 32 clients should not get much past 450 bytes, which is well within a
|
|
//reasonable range. We don't need to send anything about the max ammo or current weapon, because
|
|
//currentState.weapon can be checked for the ent in question on the client. -rww
|
|
void G_SiegeClientExData(gentity_t *msgTarg)
|
|
{
|
|
gentity_t *ent;
|
|
int count = 0;
|
|
int i = 0;
|
|
char str[MAX_STRING_CHARS];
|
|
char scratch[MAX_STRING_CHARS];
|
|
|
|
while (i < level.num_entities && count < MAX_EXDATA_ENTS_TO_SEND)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent->inuse && ent->client && msgTarg->s.number != ent->s.number &&
|
|
ent->s.eType == ET_PLAYER && msgTarg->client->sess.sessionTeam == ent->client->sess.sessionTeam &&
|
|
trap_InPVS(msgTarg->client->ps.origin, ent->client->ps.origin))
|
|
{ //another client in the same pvs, send his jive
|
|
if (count)
|
|
{ //append a seperating space if we are not the first in the list
|
|
Q_strcat(str, sizeof(str), " ");
|
|
}
|
|
else
|
|
{ //otherwise create the prepended chunk
|
|
strcpy(str, "sxd ");
|
|
}
|
|
|
|
//append the stats
|
|
Com_sprintf(scratch, sizeof(scratch), "%i|%i|%i|%i", ent->s.number, ent->client->ps.stats[STAT_HEALTH],
|
|
ent->client->ps.stats[STAT_MAX_HEALTH], ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex]);
|
|
Q_strcat(str, sizeof(str), scratch);
|
|
count++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!count)
|
|
{ //nothing to send
|
|
return;
|
|
}
|
|
|
|
//send the string to him
|
|
trap_SendServerCommand(msgTarg-g_entities, str);
|
|
}
|