PageRenderTime 10ms CodeModel.GetById 40ms app.highlight 140ms RepoModel.GetById 11ms app.codeStats 1ms

/codemp/game/g_saga.c

https://github.com/Stoiss/jaMME
C | 1963 lines | 1467 code | 298 blank | 198 comment | 364 complexity | 56a5b9fdbbdade30a1f90900af292363 MD5 | raw file
   1// Copyright (C) 2000-2002 Raven Software, Inc.
   2//
   3/*****************************************************************************
   4 * name:		g_saga.c
   5 *
   6 * desc:		Game-side module for Siege gametype.
   7 *
   8 * $Author: Rich Whitehouse $ 
   9 * $Revision: 1.6 $
  10 *
  11 *****************************************************************************/
  12#include "g_local.h"
  13#include "bg_saga.h"
  14
  15#define SIEGEITEM_STARTOFFRADAR 8
  16
  17static char		team1[512];
  18static char		team2[512];
  19
  20siegePers_t	g_siegePersistant = {qfalse, 0, 0};
  21
  22int			imperial_goals_required = 0;
  23int			imperial_goals_completed = 0;
  24int			rebel_goals_required = 0;
  25int			rebel_goals_completed = 0;
  26
  27int			imperial_time_limit = 0;
  28int			rebel_time_limit = 0;
  29
  30int			gImperialCountdown = 0;
  31int			gRebelCountdown = 0;
  32
  33int			rebel_attackers = 0;
  34int			imperial_attackers = 0;
  35
  36qboolean	gSiegeRoundBegun = qfalse;
  37qboolean	gSiegeRoundEnded = qfalse;
  38qboolean	gSiegeRoundWinningTeam = 0;
  39int			gSiegeBeginTime = Q3_INFINITE;
  40
  41int			g_preroundState = 0; //default to starting as spec (1 is starting ingame)
  42
  43void LogExit( const char *string );
  44void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin);
  45
  46static char gParseObjectives[MAX_SIEGE_INFO_SIZE];
  47static char gObjectiveCfgStr[1024];
  48
  49//go through all classes on a team and register their
  50//weapons and items for precaching.
  51void G_SiegeRegisterWeaponsAndHoldables(int team)
  52{
  53	siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team);
  54
  55	if (stm)
  56	{
  57		int i = 0;
  58		siegeClass_t *scl;
  59		while (i < stm->numClasses)
  60		{
  61			scl = stm->classes[i];
  62
  63			if (scl)
  64			{
  65				int j = 0;
  66				while (j < WP_NUM_WEAPONS)
  67				{
  68					if (scl->weapons & (1 << j))
  69					{ //we use this weapon so register it.
  70						RegisterItem(BG_FindItemForWeapon(j));
  71					}
  72					j++;
  73				}
  74				j = 0;
  75				while (j < HI_NUM_HOLDABLE)
  76				{
  77					if (scl->invenItems & (1 << j))
  78					{ //we use this item so register it.
  79						RegisterItem(BG_FindItemForHoldable(j));
  80					}
  81					j++;
  82				}
  83			}
  84			i++;
  85		}
  86	}
  87}
  88
  89//tell clients that this team won and print it on their scoreboard for intermission
  90//or whatever.
  91void SiegeSetCompleteData(int team)
  92{
  93	trap_SetConfigstring(CS_SIEGE_WINTEAM, va("%i", team));
  94}
  95
  96void InitSiegeMode(void)
  97{
  98	vmCvar_t		mapname;
  99	char			levelname[512];
 100	char			teamIcon[128];
 101	char			goalreq[64];
 102	char			teams[2048];
 103	static char objective[MAX_SIEGE_INFO_SIZE];
 104	char			objecStr[8192];
 105	int				len = 0;
 106	int				i = 0;
 107//	int				j = 0;
 108	int				objectiveNumTeam1 = 0;
 109	int				objectiveNumTeam2 = 0;
 110	fileHandle_t	f;
 111
 112	objective[0] = '\0';
 113
 114	if (level.gametype != GT_SIEGE)
 115	{
 116		goto failure;
 117	}
 118
 119	//reset
 120	SiegeSetCompleteData(0);
 121
 122	//get pers data in case it existed from last level
 123	if (g_siegeTeamSwitch.integer)
 124	{
 125		trap_SiegePersGet(&g_siegePersistant);
 126		if (g_siegePersistant.beatingTime)
 127		{
 128			trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, va("%i", g_siegePersistant.lastTime));
 129		}
 130		else
 131		{
 132			trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, "0");
 133		}
 134	}
 135	else
 136	{ //hmm, ok, nothing.
 137		trap_SetConfigstring(CS_SIEGE_TIMEOVERRIDE, "0");
 138	}
 139
 140	imperial_goals_completed = 0;
 141	rebel_goals_completed = 0;
 142
 143	trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
 144
 145	Com_sprintf(levelname, sizeof(levelname), "maps/%s.siege\0", mapname.string);
 146
 147	if ( !levelname[0] )
 148	{
 149		goto failure;
 150	}
 151
 152	len = trap_FS_FOpenFile(levelname, &f, FS_READ);
 153
 154	if (!f || len >= MAX_SIEGE_INFO_SIZE)
 155	{
 156		goto failure;
 157	}
 158
 159	trap_FS_Read(siege_info, len, f);
 160
 161	trap_FS_FCloseFile(f);
 162
 163	siege_valid = 1;
 164
 165	//See if players should be specs or ingame preround
 166	if (BG_SiegeGetPairedValue(siege_info, "preround_state", teams))
 167	{
 168		if (teams[0])
 169		{
 170			g_preroundState = atoi(teams);
 171		}
 172	}
 173
 174	if (BG_SiegeGetValueGroup(siege_info, "Teams", teams))
 175	{
 176		if (g_siegeTeam1.string[0] && Q_stricmp(g_siegeTeam1.string, "none"))
 177		{ //check for override
 178			strcpy(team1, g_siegeTeam1.string);
 179		}
 180		else
 181		{ //otherwise use level default
 182			BG_SiegeGetPairedValue(teams, "team1", team1);
 183		}
 184
 185		if (g_siegeTeam2.string[0] && Q_stricmp(g_siegeTeam2.string, "none"))
 186		{ //check for override
 187			strcpy(team2, g_siegeTeam2.string);
 188		}
 189		else
 190		{ //otherwise use level default
 191			BG_SiegeGetPairedValue(teams, "team2", team2);
 192		}
 193	}
 194	else
 195	{
 196		G_Error("Siege teams not defined");
 197	}
 198
 199	if (BG_SiegeGetValueGroup(siege_info, team2, gParseObjectives))
 200	{
 201		if (BG_SiegeGetPairedValue(gParseObjectives, "TeamIcon", teamIcon))
 202		{
 203			trap_Cvar_Set( "team2_icon", teamIcon);
 204		}
 205
 206		if (BG_SiegeGetPairedValue(gParseObjectives, "RequiredObjectives", goalreq))
 207		{
 208			rebel_goals_required = atoi(goalreq);
 209		}
 210		if (BG_SiegeGetPairedValue(gParseObjectives, "Timed", goalreq))
 211		{
 212			rebel_time_limit = atoi(goalreq)*1000;
 213			if (g_siegeTeamSwitch.integer &&
 214				g_siegePersistant.beatingTime)
 215			{
 216				gRebelCountdown = level.time + g_siegePersistant.lastTime;
 217			}
 218			else
 219			{
 220				gRebelCountdown = level.time + rebel_time_limit;
 221			}
 222		}
 223		if (BG_SiegeGetPairedValue(gParseObjectives, "attackers", goalreq))
 224		{
 225			rebel_attackers = atoi(goalreq);
 226		}
 227	}
 228
 229	if (BG_SiegeGetValueGroup(siege_info, team1, gParseObjectives))
 230	{
 231
 232		if (BG_SiegeGetPairedValue(gParseObjectives, "TeamIcon", teamIcon))
 233		{
 234			trap_Cvar_Set( "team1_icon", teamIcon);
 235		}
 236
 237		if (BG_SiegeGetPairedValue(gParseObjectives, "RequiredObjectives", goalreq))
 238		{
 239			imperial_goals_required = atoi(goalreq);
 240		}
 241		if (BG_SiegeGetPairedValue(gParseObjectives, "Timed", goalreq))
 242		{
 243			if (rebel_time_limit)
 244			{
 245				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");
 246			}
 247			else
 248			{
 249				imperial_time_limit = atoi(goalreq)*1000;
 250				if (g_siegeTeamSwitch.integer &&
 251					g_siegePersistant.beatingTime)
 252				{
 253					gImperialCountdown = level.time + g_siegePersistant.lastTime;
 254				}
 255				else
 256				{
 257					gImperialCountdown = level.time + imperial_time_limit;
 258				}
 259			}
 260		}
 261		if (BG_SiegeGetPairedValue(gParseObjectives, "attackers", goalreq))
 262		{
 263			imperial_attackers = atoi(goalreq);
 264		}
 265	}
 266
 267	//Load the player class types
 268	BG_SiegeLoadClasses(NULL);
 269
 270	if (!bgNumSiegeClasses)
 271	{ //We didn't find any?!
 272		G_Error("Couldn't find any player classes for Siege");
 273	}
 274
 275	/*
 276	//We could probably just see what teams are used on this level,
 277	//then see what classes are used by those teams, and then precache
 278	//all weapons for said classes. However, I'm just going to do them
 279	//all for now.
 280	while (i < bgNumSiegeClasses)
 281	{
 282		cl = &bgSiegeClasses[i];
 283		j = 0;
 284
 285		while (j < WP_NUM_WEAPONS)
 286		{
 287			if (cl->weapons & (1 << j))
 288			{ //we use this weapon so register it.
 289				RegisterItem(BG_FindItemForWeapon(j));
 290			}
 291
 292			j++;
 293		}
 294
 295		i++;
 296	}
 297	*/
 298	//Ok, I'm adding inventory item precaching now, so I'm finally going to optimize this
 299	//to only do weapons/items for the current teams used on the level.
 300
 301	//Now load the teams since we have class data.
 302	BG_SiegeLoadTeams();
 303
 304	if (!bgNumSiegeTeams)
 305	{ //React same as with classes.
 306		G_Error("Couldn't find any player teams for Siege");
 307	}
 308
 309	//Get and set the team themes for each team. This will control which classes can be
 310	//used on each team.
 311	if (BG_SiegeGetValueGroup(siege_info, team1, gParseObjectives))
 312	{
 313		if (BG_SiegeGetPairedValue(gParseObjectives, "UseTeam", goalreq))
 314		{
 315			BG_SiegeSetTeamTheme(SIEGETEAM_TEAM1, goalreq);
 316		}
 317
 318		//Now count up the objectives for this team.
 319		i = 1;
 320		strcpy(objecStr, va("Objective%i", i));
 321		while (BG_SiegeGetValueGroup(gParseObjectives, objecStr, objective))
 322		{
 323			objectiveNumTeam1++;
 324			i++;
 325			strcpy(objecStr, va("Objective%i", i));
 326		}
 327	}
 328	if (BG_SiegeGetValueGroup(siege_info, team2, gParseObjectives))
 329	{
 330		if (BG_SiegeGetPairedValue(gParseObjectives, "UseTeam", goalreq))
 331		{
 332			BG_SiegeSetTeamTheme(SIEGETEAM_TEAM2, goalreq);
 333		}
 334
 335		//Now count up the objectives for this team.
 336		i = 1;
 337		strcpy(objecStr, va("Objective%i", i));
 338		while (BG_SiegeGetValueGroup(gParseObjectives, objecStr, objective))
 339		{
 340			objectiveNumTeam2++;
 341			i++;
 342			strcpy(objecStr, va("Objective%i", i));
 343		}
 344	}
 345
 346	//Set the configstring to show status of all current objectives
 347	strcpy(gObjectiveCfgStr, "t1");
 348	while (objectiveNumTeam1 > 0)
 349	{ //mark them all as not completed since we just initialized
 350		Q_strcat(gObjectiveCfgStr, 1024, "-0");
 351		objectiveNumTeam1--;
 352	}
 353	//Finished doing team 1's objectives, now do team 2's
 354	Q_strcat(gObjectiveCfgStr, 1024, "|t2");
 355	while (objectiveNumTeam2 > 0)
 356	{
 357		Q_strcat(gObjectiveCfgStr, 1024, "-0");
 358		objectiveNumTeam2--;
 359	}
 360
 361	//And finally set the actual config string
 362	trap_SetConfigstring(CS_SIEGE_OBJECTIVES, gObjectiveCfgStr);
 363
 364	//precache saber data for classes that use sabers on both teams
 365	BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM1);
 366	BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM2);
 367
 368	G_SiegeRegisterWeaponsAndHoldables(SIEGETEAM_TEAM1);
 369	G_SiegeRegisterWeaponsAndHoldables(SIEGETEAM_TEAM2);
 370
 371	return;
 372
 373failure:
 374	siege_valid = 0;
 375}
 376
 377void G_SiegeSetObjectiveComplete(int team, int objective, qboolean failIt)
 378{
 379	char *p = NULL;
 380	int onObjective = 0;
 381
 382	if (team == SIEGETEAM_TEAM1)
 383	{
 384		p = strstr(gObjectiveCfgStr, "t1");
 385	}
 386	else if (team == SIEGETEAM_TEAM2)
 387	{
 388		p = strstr(gObjectiveCfgStr, "t2");
 389	}
 390
 391	if (!p)
 392	{
 393		assert(0);
 394		return;
 395	}
 396
 397	//Parse from the beginning of this team's objectives until we get to the desired objective
 398	//number.
 399	while (p && *p && *p != '|')
 400	{
 401		if (*p == '-')
 402		{
 403			onObjective++;
 404		}
 405
 406		if (onObjective == objective)
 407		{ //this is the one we want
 408			//Move to the next char, the status of this objective
 409			p++;
 410
 411			//Now change it from '0' to '1' if we are completeing the objective
 412			//or vice versa if the objective has been taken away
 413			if (failIt)
 414			{
 415				*p = '0';
 416			}
 417			else
 418			{
 419				*p = '1';
 420			}
 421			break;
 422		}
 423
 424		p++;
 425	}
 426
 427	//Now re-update the configstring.
 428	trap_SetConfigstring(CS_SIEGE_OBJECTIVES, gObjectiveCfgStr);
 429}
 430
 431//Returns qtrue if objective complete currently, otherwise qfalse
 432qboolean G_SiegeGetCompletionStatus(int team, int objective)
 433{
 434	char *p = NULL;
 435	int onObjective = 0;
 436
 437	if (team == SIEGETEAM_TEAM1)
 438	{
 439		p = strstr(gObjectiveCfgStr, "t1");
 440	}
 441	else if (team == SIEGETEAM_TEAM2)
 442	{
 443		p = strstr(gObjectiveCfgStr, "t2");
 444	}
 445
 446	if (!p)
 447	{
 448		assert(0);
 449		return qfalse;
 450	}
 451
 452	//Parse from the beginning of this team's objectives until we get to the desired objective
 453	//number.
 454	while (p && *p && *p != '|')
 455	{
 456		if (*p == '-')
 457		{
 458			onObjective++;
 459		}
 460
 461		if (onObjective == objective)
 462		{ //this is the one we want
 463			//Move to the next char, the status of this objective
 464			p++;
 465
 466			//return qtrue if it's '1', qfalse if it's anything else
 467			if (*p == '1')
 468			{
 469				return qtrue;
 470			}
 471			else
 472			{
 473				return qfalse;
 474			}
 475			break;
 476		}
 477
 478		p++;
 479	}
 480
 481	return qfalse;
 482}
 483
 484void UseSiegeTarget(gentity_t *other, gentity_t *en, char *target)
 485{ //actually use the player which triggered the object which triggered the siege objective to trigger the target
 486	gentity_t		*t;
 487	gentity_t		*ent;
 488
 489	if ( !en || !en->client )
 490	{ //looks like we don't have access to a player, so just use the activating entity
 491		ent = other;
 492	}
 493	else
 494	{
 495		ent = en;
 496	}
 497
 498	if (!en)
 499	{
 500		return;
 501	}
 502
 503	if ( !target )
 504	{
 505		return;
 506	}
 507
 508	t = NULL;
 509	while ( (t = G_Find (t, FOFS(targetname), target)) != NULL )
 510	{
 511		if ( t == ent )
 512		{
 513			G_Printf ("WARNING: Entity used itself.\n");
 514		}
 515		else
 516		{
 517			if ( t->use )
 518			{
 519				GlobalUse(t, ent, ent);
 520			}
 521		}
 522		if ( !ent->inuse )
 523		{
 524			G_Printf("entity was removed while using targets\n");
 525			return;
 526		}
 527	}
 528}
 529
 530void SiegeBroadcast_OBJECTIVECOMPLETE(int team, int client, int objective)
 531{
 532	gentity_t *te;
 533	vec3_t nomatter;
 534
 535	VectorClear(nomatter);
 536
 537	te = G_TempEntity( nomatter, EV_SIEGE_OBJECTIVECOMPLETE );
 538	te->r.svFlags |= SVF_BROADCAST;
 539	te->s.eventParm = team;
 540	te->s.weapon = client;
 541	te->s.trickedentindex = objective;
 542}
 543
 544void SiegeBroadcast_ROUNDOVER(int winningteam, int winningclient)
 545{
 546	gentity_t *te;
 547	vec3_t nomatter;
 548
 549	VectorClear(nomatter);
 550
 551	te = G_TempEntity( nomatter, EV_SIEGE_ROUNDOVER );
 552	te->r.svFlags |= SVF_BROADCAST;
 553	te->s.eventParm = winningteam;
 554	te->s.weapon = winningclient;
 555}
 556
 557void BroadcastObjectiveCompletion(int team, int objective, int final, int client)
 558{
 559	if (client != ENTITYNUM_NONE && g_entities[client].client && g_entities[client].client->sess.sessionTeam == team)
 560	{ //guy who completed this objective gets points, providing he's on the opposing team
 561		AddScore(&g_entities[client], g_entities[client].client->ps.origin, SIEGE_POINTS_OBJECTIVECOMPLETED);
 562	}
 563
 564	SiegeBroadcast_OBJECTIVECOMPLETE(team, client, objective);
 565	//G_Printf("Broadcast goal completion team %i objective %i final %i\n", team, objective, final);
 566}
 567
 568void AddSiegeWinningTeamPoints(int team, int winner)
 569{
 570	int i = 0;
 571	gentity_t *ent;
 572
 573	while (i < MAX_CLIENTS)
 574	{
 575		ent = &g_entities[i];
 576
 577		if (ent && ent->client && ent->client->sess.sessionTeam == team)
 578		{
 579			if (i == winner)
 580			{
 581				AddScore(ent, ent->client->ps.origin, SIEGE_POINTS_TEAMWONROUND+SIEGE_POINTS_FINALOBJECTIVECOMPLETED);
 582			}
 583			else
 584			{
 585				AddScore(ent, ent->client->ps.origin, SIEGE_POINTS_TEAMWONROUND);
 586			}
 587		}
 588
 589		i++;
 590	}
 591}
 592
 593void SiegeClearSwitchData(void)
 594{
 595	memset(&g_siegePersistant, 0, sizeof(g_siegePersistant));
 596	trap_SiegePersSet(&g_siegePersistant);
 597}
 598
 599void SiegeDoTeamAssign(void)
 600{
 601	int i = 0;
 602	gentity_t *ent;
 603
 604	//yeah, this is great...
 605	while (i < MAX_CLIENTS)
 606	{
 607		ent = &g_entities[i];
 608
 609		if (ent->inuse && ent->client &&
 610			ent->client->pers.connected == CON_CONNECTED)
 611		{ //a connected client, switch his frickin teams around
 612			if (ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM1)
 613			{
 614				ent->client->sess.siegeDesiredTeam = SIEGETEAM_TEAM2;
 615			}
 616			else if (ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM2)
 617			{
 618				ent->client->sess.siegeDesiredTeam = SIEGETEAM_TEAM1;
 619			}
 620
 621			if (ent->client->sess.sessionTeam == SIEGETEAM_TEAM1)
 622			{
 623				SetTeamQuick(ent, SIEGETEAM_TEAM2, qfalse);
 624			}
 625			else if (ent->client->sess.sessionTeam == SIEGETEAM_TEAM2)
 626			{
 627				SetTeamQuick(ent, SIEGETEAM_TEAM1, qfalse);
 628			}
 629		}
 630		i++;
 631	}
 632}
 633
 634void SiegeTeamSwitch(int winTeam, int winTime)
 635{
 636	trap_SiegePersGet(&g_siegePersistant);
 637	if (g_siegePersistant.beatingTime)
 638	{ //was already in "switched" mode, change back
 639		//announce the winning team.
 640		//either the first team won again, or the second
 641		//team beat the time set by the initial team. In any
 642		//case the winTeam here is the overall winning team.
 643		SiegeSetCompleteData(winTeam);
 644		SiegeClearSwitchData();
 645	}
 646	else
 647	{ //go into "beat their time" mode
 648		g_siegePersistant.beatingTime = qtrue;
 649        g_siegePersistant.lastTeam = winTeam;
 650		g_siegePersistant.lastTime = winTime;
 651
 652		trap_SiegePersSet(&g_siegePersistant);
 653	}
 654}
 655
 656void SiegeRoundComplete(int winningteam, int winningclient)
 657{
 658	vec3_t nomatter;
 659	char teamstr[1024];
 660	int originalWinningClient = winningclient;
 661
 662	//G_Printf("Team %i won\n", winningteam);
 663
 664	if (winningclient != ENTITYNUM_NONE && g_entities[winningclient].client &&
 665		g_entities[winningclient].client->sess.sessionTeam != winningteam)
 666	{ //this person just won the round for the other team..
 667		winningclient = ENTITYNUM_NONE;
 668	}
 669
 670	VectorClear(nomatter);
 671
 672	SiegeBroadcast_ROUNDOVER(winningteam, winningclient);
 673
 674	AddSiegeWinningTeamPoints(winningteam, winningclient);
 675
 676	//Instead of exiting like this, fire off a target, and let it handle things.
 677	//Can be a script or whatever the designer wants.
 678   	if (winningteam == SIEGETEAM_TEAM1)
 679	{
 680		Com_sprintf(teamstr, sizeof(teamstr), team1);
 681	}
 682	else
 683	{
 684		Com_sprintf(teamstr, sizeof(teamstr), team2);
 685	}
 686
 687	trap_SetConfigstring(CS_SIEGE_STATE, va("3|%i", level.time)); //ended
 688	gSiegeRoundBegun = qfalse;
 689	gSiegeRoundEnded = qtrue;
 690	gSiegeRoundWinningTeam = winningteam;
 691
 692	if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
 693	{
 694		if (!BG_SiegeGetPairedValue(gParseObjectives, "roundover_target", teamstr))
 695		{ //didn't find the name of the thing to target upon win, just logexit now then.
 696			LogExit( "Objectives completed" );
 697			return;
 698		}
 699		
 700		if (originalWinningClient == ENTITYNUM_NONE)
 701		{ //oh well, just find something active and use it then.
 702            int i = 0;
 703			gentity_t *ent;
 704
 705			while (i < MAX_CLIENTS)
 706			{
 707				ent = &g_entities[i];
 708
 709				if (ent->inuse)
 710				{ //sure, you'll do.
 711                    originalWinningClient = ent->s.number;
 712					break;
 713				}
 714
 715				i++;
 716			}
 717		}
 718		G_UseTargets2(&g_entities[originalWinningClient], &g_entities[originalWinningClient], teamstr);
 719	}
 720
 721	if (g_siegeTeamSwitch.integer &&
 722		(imperial_time_limit || rebel_time_limit))
 723	{ //handle stupid team switching crap
 724		int time = 0;
 725		if (imperial_time_limit)
 726		{
 727			time = imperial_time_limit-(gImperialCountdown-level.time);
 728		}
 729		else if (rebel_time_limit)
 730		{
 731			time = rebel_time_limit-(gRebelCountdown-level.time);
 732		}
 733
 734		if (time < 1)
 735		{
 736			time = 1;
 737		}
 738		SiegeTeamSwitch(winningteam, time);
 739	}
 740	else
 741	{ //assure it's clear for next round
 742		SiegeClearSwitchData();
 743	}
 744}
 745
 746void G_ValidateSiegeClassForTeam(gentity_t *ent, int team)
 747{
 748	siegeClass_t *scl;
 749	siegeTeam_t *stm;
 750	int newClassIndex = -1;
 751	if (ent->client->siegeClass == -1)
 752	{ //uh.. sure.
 753		return;
 754	}
 755
 756	scl = &bgSiegeClasses[ent->client->siegeClass];
 757
 758	stm = BG_SiegeFindThemeForTeam(team);
 759	if (stm)
 760	{
 761		int i = 0;
 762
 763		while (i < stm->numClasses)
 764		{ //go through the team and see its valid classes, can we find one that matches our current player class?
 765			if (stm->classes[i])
 766			{
 767				if (!Q_stricmp(scl->name, stm->classes[i]->name))
 768				{ //the class we're using is already ok for this team.
 769					return;
 770				}
 771				if (stm->classes[i]->playerClass == scl->playerClass ||
 772					newClassIndex == -1)
 773				{
 774					newClassIndex = i;
 775				}
 776			}
 777			i++;
 778		}
 779
 780		if (newClassIndex != -1)
 781		{ //ok, let's find it in the global class array
 782			ent->client->siegeClass = BG_SiegeFindClassIndexByName(stm->classes[newClassIndex]->name);
 783			strcpy(ent->client->sess.siegeClass, stm->classes[newClassIndex]->name);
 784		}
 785	}
 786}
 787
 788//bypass most of the normal checks in SetTeam
 789void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin)
 790{
 791	char userinfo[MAX_INFO_STRING];
 792
 793	trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) );
 794
 795	if (level.gametype == GT_SIEGE)
 796	{
 797		G_ValidateSiegeClassForTeam(ent, team);
 798	}
 799
 800	ent->client->sess.sessionTeam = team;
 801
 802	if (team == TEAM_SPECTATOR)
 803	{
 804		ent->client->sess.spectatorState = SPECTATOR_FREE;
 805		Info_SetValueForKey(userinfo, "team", "s");
 806	}
 807	else
 808	{
 809		ent->client->sess.spectatorState = SPECTATOR_NOT;
 810		if (team == TEAM_RED)
 811		{
 812			Info_SetValueForKey(userinfo, "team", "r");
 813		}
 814		else if (team == TEAM_BLUE)
 815		{
 816			Info_SetValueForKey(userinfo, "team", "b");
 817		}
 818		else
 819		{
 820			Info_SetValueForKey(userinfo, "team", "?");
 821		}
 822	}
 823
 824	trap_SetUserinfo( ent->s.number, userinfo );
 825
 826	ent->client->sess.spectatorClient = 0;
 827
 828	ent->client->pers.teamState.state = TEAM_BEGIN;
 829
 830	if ( !ClientUserinfoChanged( ent->s.number ) )
 831		return;
 832
 833	if (doBegin)
 834	{
 835		ClientBegin( ent->s.number, qfalse );
 836	}
 837}
 838
 839void SiegeRespawn(gentity_t *ent)
 840{
 841	gentity_t *tent;
 842
 843	if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam)
 844	{
 845		SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qtrue);
 846	}
 847	else
 848	{
 849		ClientSpawn(ent);
 850		// add a teleportation effect
 851		tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
 852		tent->s.clientNum = ent->s.clientNum;
 853	}
 854}
 855
 856void SiegeBeginRound(int entNum)
 857{ //entNum is just used as something to fire targets from.
 858	char targname[1024];
 859
 860	if (!g_preroundState)
 861	{ //if players are not ingame on round start then respawn them now
 862		int i = 0;
 863		gentity_t *ent;
 864		qboolean spawnEnt = qfalse;
 865
 866		//respawn everyone now
 867		while (i < MAX_CLIENTS)
 868		{
 869			ent = &g_entities[i];
 870
 871			if (ent->inuse && ent->client)
 872			{
 873				if (ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
 874					!(ent->client->ps.pm_flags & PMF_FOLLOW))
 875				{ //not a spec, just respawn them
 876					spawnEnt = qtrue;
 877				}
 878				else if (ent->client->sess.sessionTeam == TEAM_SPECTATOR &&
 879					(ent->client->sess.siegeDesiredTeam == TEAM_RED ||
 880					 ent->client->sess.siegeDesiredTeam == TEAM_BLUE))
 881				{ //spectator but has a desired team
 882					spawnEnt = qtrue;
 883				}
 884			}
 885
 886			if (spawnEnt)
 887			{
 888				SiegeRespawn(ent);
 889
 890				spawnEnt = qfalse;
 891			}
 892			i++;
 893		}
 894	}
 895
 896	//Now check if there's something to fire off at the round start, if so do it.
 897	if (BG_SiegeGetPairedValue(siege_info, "roundbegin_target", targname))
 898	{
 899		if (targname[0])
 900		{
 901			G_UseTargets2(&g_entities[entNum], &g_entities[entNum], targname);
 902		}
 903	}
 904
 905	trap_SetConfigstring(CS_SIEGE_STATE, va("0|%i", level.time)); //we're ready to g0g0g0
 906}
 907
 908void SiegeCheckTimers(void)
 909{
 910	int i=0;
 911	gentity_t *ent;
 912	int numTeam1 = 0;
 913	int numTeam2 = 0;
 914
 915	if (level.gametype != GT_SIEGE)
 916	{
 917		return;
 918	}
 919
 920	if (level.intermissiontime)
 921	{
 922		return;
 923	}
 924
 925	if (gSiegeRoundEnded)
 926	{
 927		return;
 928	}
 929
 930	if (!gSiegeRoundBegun)
 931	{ //check if anyone is active on this team - if not, keep the timer set up.
 932		i = 0;
 933
 934		while (i < MAX_CLIENTS)
 935		{
 936			ent = &g_entities[i];
 937
 938			if (ent && ent->inuse && ent->client &&
 939				ent->client->pers.connected == CON_CONNECTED &&
 940				ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM1)
 941			{
 942				numTeam1++;
 943			}
 944			i++;
 945		}
 946
 947		i = 0;
 948
 949		while (i < MAX_CLIENTS)
 950		{
 951			ent = &g_entities[i];
 952
 953			if (ent && ent->inuse && ent->client &&
 954				ent->client->pers.connected == CON_CONNECTED &&
 955				ent->client->sess.siegeDesiredTeam == SIEGETEAM_TEAM2)
 956			{
 957				numTeam2++;
 958			}
 959			i++;
 960		}
 961
 962		if (g_siegeTeamSwitch.integer &&
 963			g_siegePersistant.beatingTime)
 964		{
 965			gImperialCountdown = level.time + g_siegePersistant.lastTime;
 966			gRebelCountdown = level.time + g_siegePersistant.lastTime;
 967		}
 968		else
 969		{
 970			gImperialCountdown = level.time + imperial_time_limit;
 971			gRebelCountdown = level.time + rebel_time_limit;
 972		}
 973	}
 974
 975	if (imperial_time_limit)
 976	{ //team1
 977		if (gImperialCountdown < level.time)
 978		{
 979			SiegeRoundComplete(SIEGETEAM_TEAM2, ENTITYNUM_NONE);
 980			imperial_time_limit = 0;
 981			return;
 982		}
 983	}
 984
 985	if (rebel_time_limit)
 986	{ //team2
 987		if (gRebelCountdown < level.time)
 988		{
 989			SiegeRoundComplete(SIEGETEAM_TEAM1, ENTITYNUM_NONE);
 990			rebel_time_limit = 0;
 991			return;
 992		}
 993	}
 994
 995	if (!gSiegeRoundBegun)
 996	{
 997		if (!numTeam1 || !numTeam2)
 998		{ //don't have people on both teams yet.
 999			gSiegeBeginTime = level.time + SIEGE_ROUND_BEGIN_TIME;
1000			trap_SetConfigstring(CS_SIEGE_STATE, "1"); //"waiting for players on both teams"
1001		}
1002		else if (gSiegeBeginTime < level.time)
1003		{ //mark the round as having begun
1004			gSiegeRoundBegun = qtrue;
1005			SiegeBeginRound(i); //perform any round start tasks
1006		}
1007		else if (gSiegeBeginTime > (level.time + SIEGE_ROUND_BEGIN_TIME))
1008		{
1009			gSiegeBeginTime = level.time + SIEGE_ROUND_BEGIN_TIME;
1010		}
1011		else
1012		{
1013			trap_SetConfigstring(CS_SIEGE_STATE, va("2|%i", gSiegeBeginTime - SIEGE_ROUND_BEGIN_TIME)); //getting ready to begin
1014		}
1015	}
1016}
1017
1018void SiegeObjectiveCompleted(int team, int objective, int final, int client)
1019{
1020	int goals_completed, goals_required;
1021
1022	if (gSiegeRoundEnded)
1023	{
1024		return;
1025	}
1026
1027	//Update the configstring status
1028	G_SiegeSetObjectiveComplete(team, objective, qfalse);
1029
1030	if (final != -1)
1031	{
1032		if (team == SIEGETEAM_TEAM1)
1033		{
1034			imperial_goals_completed++;
1035		}
1036		else
1037		{
1038			rebel_goals_completed++;
1039		}
1040	}
1041
1042	if (team == SIEGETEAM_TEAM1)
1043	{
1044		goals_completed = imperial_goals_completed;
1045		goals_required = imperial_goals_required;
1046	}
1047	else
1048	{
1049		goals_completed = rebel_goals_completed;
1050		goals_required = rebel_goals_required;
1051	}
1052
1053	if (final == 1 || goals_completed >= goals_required)
1054	{
1055		SiegeRoundComplete(team, client);
1056	}
1057	else
1058	{
1059		BroadcastObjectiveCompletion(team, objective, final, client);
1060	}
1061}
1062
1063void siegeTriggerUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
1064{
1065	char			teamstr[64];
1066	char			objectivestr[64];
1067	static char		desiredobjective[MAX_SIEGE_INFO_SIZE];
1068	int				clUser = ENTITYNUM_NONE;
1069	int				final = 0;
1070	int				i = 0;
1071
1072	desiredobjective[0] = '\0';
1073
1074	if (!siege_valid)
1075	{
1076		return;
1077	}
1078
1079	if (!(ent->s.eFlags & EF_RADAROBJECT))
1080	{ //toggle radar on and exit if it is not showing up already
1081		ent->s.eFlags |= EF_RADAROBJECT;
1082		return;
1083	}
1084
1085	if (activator && activator->client)
1086	{ //activator will hopefully be the person who triggered this event
1087		clUser = activator->s.number;
1088	}
1089
1090	if (ent->side == SIEGETEAM_TEAM1)
1091	{
1092		Com_sprintf(teamstr, sizeof(teamstr), team1);
1093	}
1094	else
1095	{
1096		Com_sprintf(teamstr, sizeof(teamstr), team2);
1097	}
1098
1099	if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
1100	{
1101		Com_sprintf(objectivestr, sizeof(objectivestr), "Objective%i", ent->objective);
1102
1103		if (BG_SiegeGetValueGroup(gParseObjectives, objectivestr, desiredobjective))
1104		{
1105			if (BG_SiegeGetPairedValue(desiredobjective, "final", teamstr))
1106			{
1107				final = atoi(teamstr);
1108			}
1109
1110			if (BG_SiegeGetPairedValue(desiredobjective, "target", teamstr))
1111			{
1112				while (teamstr[i])
1113				{
1114					if (teamstr[i] == '\r' ||
1115						teamstr[i] == '\n')
1116					{
1117						teamstr[i] = '\0';
1118					}
1119
1120					i++;
1121				}
1122				UseSiegeTarget(other, activator, teamstr);
1123			}
1124
1125			if (ent->target && ent->target[0])
1126			{ //use this too
1127				UseSiegeTarget(other, activator, ent->target);
1128			}
1129
1130			SiegeObjectiveCompleted(ent->side, ent->objective, final, clUser);
1131		}
1132	}
1133}
1134
1135/*QUAKED info_siege_objective (1 0 1) (-16 -16 -24) (16 16 32) ? x x STARTOFFRADAR
1136STARTOFFRADAR - start not displaying on radar, don't display until used.
1137
1138"objective" - specifies the objective to complete upon activation
1139"side" - set to 1 to specify an imperial goal, 2 to specify rebels
1140"icon" - icon that represents the objective on the radar
1141*/
1142void SP_info_siege_objective (gentity_t *ent)
1143{
1144	char* s;
1145
1146	if (!siege_valid || level.gametype != GT_SIEGE)
1147	{
1148		G_FreeEntity(ent);
1149		return;
1150	}
1151
1152	ent->use = siegeTriggerUse;
1153	G_SpawnInt( "objective", "0", &ent->objective);
1154	G_SpawnInt( "side", "0", &ent->side);
1155
1156	if (!ent->objective || !ent->side)
1157	{ //j00 fux0red something up
1158		G_FreeEntity(ent);
1159		G_Printf("ERROR: info_siege_objective without an objective or side value\n");
1160		return;
1161	}
1162
1163	//Set it up to be drawn on radar
1164	if (!(ent->spawnflags & SIEGEITEM_STARTOFFRADAR))
1165	{
1166		ent->s.eFlags |= EF_RADAROBJECT;
1167	}
1168
1169	//All clients want to know where it is at all times for radar
1170	ent->r.svFlags |= SVF_BROADCAST;
1171
1172	G_SpawnString( "icon", "", &s );
1173	
1174	if (s && s[0])
1175	{ 
1176		// We have an icon, so index it now.  We are reusing the genericenemyindex
1177		// variable rather than adding a new one to the entity state.
1178		ent->s.genericenemyindex = G_IconIndex(s);
1179	}
1180
1181	ent->s.brokenLimbs = ent->side;
1182	ent->s.frame = ent->objective;
1183	trap_LinkEntity(ent);
1184}
1185
1186
1187void SiegeIconUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
1188{ //toggle it on and off
1189	if (ent->s.eFlags & EF_RADAROBJECT)
1190	{
1191		ent->s.eFlags &= ~EF_RADAROBJECT;
1192		ent->r.svFlags &= ~SVF_BROADCAST;
1193	}
1194	else
1195	{
1196		ent->s.eFlags |= EF_RADAROBJECT;
1197		ent->r.svFlags |= SVF_BROADCAST;
1198	}
1199}
1200
1201/*QUAKED info_siege_radaricon (1 0 1) (-16 -16 -24) (16 16 32) ? 
1202Used to arbitrarily display radar icons at placed location. Can be used
1203to toggle on and off.
1204
1205"icon" - icon that represents the objective on the radar
1206"startoff" - if 1 start off
1207*/
1208void SP_info_siege_radaricon (gentity_t *ent)
1209{
1210	char* s;
1211	int i;
1212
1213	if (!siege_valid || level.gametype != GT_SIEGE)
1214	{
1215		G_FreeEntity(ent);
1216		return;
1217	}
1218
1219	G_SpawnInt("startoff", "0", &i);
1220
1221	if (!i)
1222	{ //start on then
1223		ent->s.eFlags |= EF_RADAROBJECT;
1224		ent->r.svFlags |= SVF_BROADCAST;
1225	}
1226
1227	G_SpawnString( "icon", "", &s );
1228	if (!s || !s[0])
1229	{ //that's the whole point of the entity
1230        Com_Error(ERR_DROP, "misc_siege_radaricon without an icon");
1231		return;
1232	}
1233
1234	ent->use = SiegeIconUse;
1235
1236	ent->s.genericenemyindex = G_IconIndex(s);
1237
1238	trap_LinkEntity(ent);
1239}
1240
1241void decompTriggerUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
1242{
1243	int final = 0;
1244	char teamstr[1024];
1245	char objectivestr[64];
1246	static char desiredobjective[MAX_SIEGE_INFO_SIZE];
1247
1248	desiredobjective[0] = '\0';
1249
1250	if (gSiegeRoundEnded)
1251	{
1252		return;
1253	}
1254
1255	if (!G_SiegeGetCompletionStatus(ent->side, ent->objective))
1256	{ //if it's not complete then there's nothing to do here
1257		return;
1258	}
1259
1260	//Update the configstring status
1261	G_SiegeSetObjectiveComplete(ent->side, ent->objective, qtrue);
1262
1263	//Find out if this objective counts toward the final objective count
1264   	if (ent->side == SIEGETEAM_TEAM1)
1265	{
1266		Com_sprintf(teamstr, sizeof(teamstr), team1);
1267	}
1268	else
1269	{
1270		Com_sprintf(teamstr, sizeof(teamstr), team2);
1271	}
1272
1273	if (BG_SiegeGetValueGroup(siege_info, teamstr, gParseObjectives))
1274	{
1275		Com_sprintf(objectivestr, sizeof(objectivestr), "Objective%i", ent->objective);
1276
1277		if (BG_SiegeGetValueGroup(gParseObjectives, objectivestr, desiredobjective))
1278		{
1279			if (BG_SiegeGetPairedValue(desiredobjective, "final", teamstr))
1280			{
1281				final = atoi(teamstr);
1282			}
1283		}
1284	}
1285
1286	//Subtract the goal num if applicable
1287	if (final != -1)
1288	{
1289		if (ent->side == SIEGETEAM_TEAM1)
1290		{
1291			imperial_goals_completed--;
1292		}
1293		else
1294		{
1295			rebel_goals_completed--;
1296		}
1297	}
1298}
1299
1300/*QUAKED info_siege_decomplete (1 0 1) (-16 -16 -24) (16 16 32)
1301"objective" - specifies the objective to decomplete upon activation
1302"side" - set to 1 to specify an imperial (team1) goal, 2 to specify rebels (team2)
1303*/
1304void SP_info_siege_decomplete (gentity_t *ent)
1305{
1306	if (!siege_valid || level.gametype != GT_SIEGE)
1307	{
1308		G_FreeEntity(ent);
1309		return;
1310	}
1311
1312	ent->use = decompTriggerUse;
1313	G_SpawnInt( "objective", "0", &ent->objective);
1314	G_SpawnInt( "side", "0", &ent->side);
1315
1316	if (!ent->objective || !ent->side)
1317	{ //j00 fux0red something up
1318		G_FreeEntity(ent);
1319		G_Printf("ERROR: info_siege_objective_decomplete without an objective or side value\n");
1320		return;
1321	}
1322}
1323
1324void siegeEndUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
1325{
1326	LogExit("Round ended");
1327}
1328
1329/*QUAKED target_siege_end (1 0 1) (-16 -16 -24) (16 16 32)
1330Do a logexit for siege when used.
1331*/
1332void SP_target_siege_end (gentity_t *ent)
1333{
1334	if (!siege_valid || level.gametype != GT_SIEGE)
1335	{
1336		G_FreeEntity(ent);
1337		return;
1338	}
1339
1340	ent->use = siegeEndUse;
1341}
1342
1343#define SIEGE_ITEM_RESPAWN_TIME 20000
1344
1345void SiegeItemRemoveOwner(gentity_t *ent, gentity_t *carrier)
1346{
1347	ent->genericValue2 = 0; //Remove picked-up flag
1348
1349	ent->genericValue8 = ENTITYNUM_NONE; //Mark entity carrying us as none
1350
1351	if (carrier)
1352	{
1353		carrier->client->holdingObjectiveItem = 0; //The carrier is no longer carrying us
1354		carrier->r.svFlags &= ~SVF_BROADCAST;
1355	}
1356}
1357
1358static void SiegeItemRespawnEffect(gentity_t *ent, vec3_t newOrg)
1359{
1360	vec3_t upAng;
1361
1362	if (ent->target5 && ent->target5[0])
1363	{
1364		G_UseTargets2(ent, ent, ent->target5);
1365	}
1366
1367	if (!ent->genericValue10)
1368	{ //no respawn effect
1369		return;
1370	}
1371
1372	VectorSet(upAng, 0, 0, 1);
1373
1374	//Play it once on the current origin, and once on the origin we're respawning to.
1375	G_PlayEffectID(ent->genericValue10, ent->r.currentOrigin, upAng);
1376	G_PlayEffectID(ent->genericValue10, newOrg, upAng);
1377}
1378
1379static void SiegeItemRespawnOnOriginalSpot(gentity_t *ent, gentity_t *carrier)
1380{
1381	SiegeItemRespawnEffect(ent, ent->pos1);
1382	G_SetOrigin(ent, ent->pos1);
1383	SiegeItemRemoveOwner(ent, carrier);
1384	
1385	// Stop the item from flashing on the radar
1386	ent->s.time2 = 0;
1387}
1388
1389void SiegeItemThink(gentity_t *ent)
1390{
1391	gentity_t *carrier = NULL;
1392
1393	if (ent->genericValue12)
1394	{ //recharge health
1395		if (ent->health > 0 && ent->health < ent->maxHealth && ent->genericValue14 < level.time)
1396		{
1397            ent->genericValue14 = level.time + ent->genericValue13;
1398			ent->health += ent->genericValue12;
1399			if (ent->health > ent->maxHealth)
1400			{
1401				ent->health = ent->maxHealth;
1402			}
1403		}
1404	}
1405
1406	if (ent->genericValue8 != ENTITYNUM_NONE)
1407	{ //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.
1408		carrier = &g_entities[ent->genericValue8];
1409
1410		if (carrier->inuse && carrier->client)
1411		{
1412			VectorCopy(carrier->client->ps.origin, ent->r.currentOrigin);
1413			trap_LinkEntity(ent);
1414		}
1415	}
1416	else if (ent->genericValue1)
1417	{ //this means we want to run physics on the object
1418		G_RunExPhys(ent, ent->radius, ent->mass, ent->random, qfalse, NULL, 0);
1419	}
1420
1421	//Bolt us to whoever is carrying us if a client
1422	if (ent->genericValue8 < MAX_CLIENTS)
1423	{
1424		ent->s.boltToPlayer = ent->genericValue8+1;
1425	}
1426	else
1427	{
1428		ent->s.boltToPlayer = 0;
1429	}
1430
1431
1432	if (carrier) {
1433		//This checking can be a bit iffy on the death stuff, but in theory we should always
1434		//get a think in before the default minimum respawn time is exceeded.
1435		if (!carrier->inuse || !carrier->client ||
1436			(carrier->client->sess.sessionTeam != SIEGETEAM_TEAM1 && carrier->client->sess.sessionTeam != SIEGETEAM_TEAM2) ||
1437			(carrier->client->ps.pm_flags & PMF_FOLLOW))
1438		{ //respawn on the original spot
1439			SiegeItemRespawnOnOriginalSpot(ent, NULL);
1440		}
1441		else if (carrier->health < 1)
1442		{ //The carrier died so pop out where he is (unless in nodrop).
1443			if (ent->target6 && ent->target6[0])
1444			{
1445				G_UseTargets2(ent, ent, ent->target6);
1446			}
1447
1448			if ( trap_PointContents(carrier->client->ps.origin, carrier->s.number) & CONTENTS_NODROP )
1449			{ //In nodrop land, go back to the original spot.
1450				SiegeItemRespawnOnOriginalSpot(ent, carrier);
1451			}
1452			else
1453			{
1454				//perform a startsolid check to make sure the seige item doesn't get stuck
1455				//in a wall or something
1456				trace_t tr;
1457				trap_Trace(&tr, carrier->client->ps.origin, ent->r.mins, ent->r.maxs, carrier->client->ps.origin, ent->s.number, ent->clipmask);
1458
1459				if(tr.startsolid)
1460				{//bad spawning area, try again with the trace up a bit.
1461					vec3_t TracePoint;
1462					VectorCopy(carrier->client->ps.origin, TracePoint);
1463					TracePoint[2] += 30;
1464					trap_Trace(&tr, TracePoint, ent->r.mins, ent->r.maxs, TracePoint, ent->s.number, ent->clipmask);
1465					
1466					if(tr.startsolid)
1467					{//hmm, well that didn't work. try one last time with the item back 
1468						//away from where the dude was facing (in case the carrier was
1469						//close to something they were attacking.)
1470						vec3_t fwd;
1471						AngleVectors(carrier->client->ps.viewangles,fwd, NULL, NULL);
1472						VectorMA(TracePoint, -30, fwd, TracePoint);
1473						trap_Trace(&tr, TracePoint, ent->r.mins, ent->r.maxs, TracePoint, ent->s.number, ent->clipmask);
1474						
1475						if(tr.startsolid)
1476						{
1477							SiegeItemRespawnOnOriginalSpot(ent, carrier);
1478							return;
1479						}
1480					}
1481
1482					G_SetOrigin(ent, TracePoint);
1483				}
1484				else
1485				{//we're good at the player's origin
1486					G_SetOrigin(ent, carrier->client->ps.origin);
1487				}
1488				
1489
1490				//G_SetOrigin(ent, carrier->client->ps.origin);
1491				ent->epVelocity[0] = Q_irand(-80, 80);
1492				ent->epVelocity[1] = Q_irand(-80, 80);
1493				ent->epVelocity[2] = Q_irand(40, 80);
1494
1495				//We're in a nonstandard place, so if we go this long without being touched,
1496				//assume we may not be reachable and respawn on the original spot.
1497				ent->genericValue9 = level.time + SIEGE_ITEM_RESPAWN_TIME;
1498
1499				SiegeItemRemoveOwner(ent, carrier);
1500			}
1501		}
1502	}
1503
1504	if (ent->genericValue9 && ent->genericValue9 < level.time)
1505	{ //time to respawn on the original spot then
1506		SiegeItemRespawnEffect(ent, ent->pos1);
1507		G_SetOrigin(ent, ent->pos1);
1508		ent->genericValue9 = 0;
1509		
1510		// stop flashing on radar
1511		ent->s.time2 = 0;
1512	}
1513
1514	ent->nextthink = level.time + FRAMETIME/2;
1515}
1516
1517void SiegeItemTouch( gentity_t *self, gentity_t *other, trace_t *trace )
1518{
1519	if (!other || !other->inuse ||
1520		!other->client || other->s.eType == ET_NPC)
1521	{
1522		if (trace && trace->startsolid)
1523		{ //let me out! (ideally this should not happen, but such is life)
1524			vec3_t escapePos;
1525			VectorCopy(self->r.currentOrigin, escapePos);
1526			escapePos[2] += 1.0f;
1527
1528			//I hope you weren't stuck in the ceiling.
1529			G_SetOrigin(self, escapePos);
1530		}
1531		return;
1532	}
1533
1534	if (other->health < 1)
1535	{ //dead people can't pick us up.
1536		return;
1537	}
1538
1539	if (other->client->holdingObjectiveItem)
1540	{ //this guy's already carrying a siege item
1541		return;
1542	}
1543
1544	if ( other->client->ps.pm_type == PM_SPECTATOR )
1545	{//spectators don't pick stuff up
1546		return;
1547	}
1548
1549	if (self->genericValue2)
1550	{ //Am I already picked up?
1551		return;
1552	}
1553
1554	if (self->genericValue6 == other->client->sess.sessionTeam)
1555	{ //Set to not be touchable by players on this team.
1556		return;
1557	}
1558
1559	if (!gSiegeRoundBegun)
1560	{ //can't pick it up if round hasn't started yet
1561		return;
1562	}
1563
1564	if (self->noise_index)
1565	{ //play the pickup noise.
1566		G_Sound(other, CHAN_AUTO, self->noise_index);
1567	}
1568
1569	self->genericValue2 = 1; //Mark it as picked up.
1570
1571	other->client->holdingObjectiveItem = self->s.number;
1572	other->r.svFlags |= SVF_BROADCAST; //broadcast player while he carries this
1573	self->genericValue8 = other->s.number; //Keep the index so we know who is "carrying" us
1574
1575	self->genericValue9 = 0; //So it doesn't think it has to respawn.
1576
1577	if (self->target2 && self->target2[0] && (!self->genericValue4 || !self->genericValue5))
1578	{ //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.
1579		G_UseTargets2(self, self, self->target2);
1580		self->genericValue5 = 1; //mark it as having been picked up
1581	}	
1582	
1583	// time2 set to -1 will blink the item on the radar indefinately
1584	self->s.time2 = 0xFFFFFFFF;
1585}
1586
1587void SiegeItemPain(gentity_t *self, gentity_t *attacker, int damage)
1588{
1589	// Time 2 is used to pulse the radar icon to show its under attack
1590	self->s.time2 = level.time;
1591}
1592
1593void SiegeItemDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
1594{
1595	self->takedamage = qfalse; //don't die more than once
1596
1597	if (self->genericValue3)
1598	{ //An indexed effect to play on death
1599		vec3_t upAng;
1600
1601		VectorSet(upAng, 0, 0, 1);
1602		G_PlayEffectID(self->genericValue3, self->r.currentOrigin, upAng);
1603	}
1604
1605	self->neverFree = qfalse;
1606	self->think = G_FreeEntity;
1607	self->nextthink = level.time;
1608
1609	//Fire off the death target if we've got one.
1610	if (self->target4 && self->target4[0])
1611	{
1612		G_UseTargets2(self, self, self->target4);
1613	}
1614}
1615
1616void SiegeItemUse(gentity_t *ent, gentity_t *other, gentity_t *activator)
1617{ //once used, become active
1618	if (ent->spawnflags & SIEGEITEM_STARTOFFRADAR)
1619	{ //start showing on radar
1620		ent->s.eFlags |= EF_RADAROBJECT;
1621
1622		if (!(ent->s.eFlags & EF_NODRAW))
1623		{ //we've nothing else to do here
1624			return;
1625		}
1626	}
1627	else
1628	{ //make sure it's showing up
1629		ent->s.eFlags |= EF_RADAROBJECT;
1630	}
1631
1632	if (ent->genericValue11 || !ent->takedamage)
1633	{ //We want to be able to walk into it to pick it up then.
1634		ent->r.contents = CONTENTS_TRIGGER;
1635		ent->clipmask = CONTENTS_SOLID|CONTENTS_TERRAIN;
1636		if (ent->genericValue11)
1637		{
1638			ent->touch = SiegeItemTouch;
1639		}
1640	}
1641	else
1642	{ //Make it solid.
1643		ent->r.contents = MASK_PLAYERSOLID;
1644		ent->clipmask = MASK_PLAYERSOLID;
1645	}
1646
1647	ent->think = SiegeItemThink;
1648	ent->nextthink = level.time + FRAMETIME/2;
1649
1650	//take off nodraw
1651	ent->s.eFlags &= ~EF_NODRAW;
1652
1653	if (ent->paintarget && ent->paintarget[0])
1654	{ //want to be on this guy's origin now then
1655		gentity_t *targ = G_Find (NULL, FOFS(targetname), ent->paintarget);
1656		
1657		if (targ && targ->inuse)
1658		{
1659			//perform a startsolid check to make sure the seige item doesn't get stuck
1660			//in a wall or something
1661			trace_t tr;
1662			vec3_t TracePoint;
1663			VectorCopy(targ->r.currentOrigin, TracePoint);
1664			trap_Trace(&tr, targ->r.currentOrigin, ent->r.mins, ent->r.maxs, 
1665				targ->r.currentOrigin, targ->s.number, ent->clipmask);
1666
1667			if(tr.startsolid)
1668			{//bad spawning area, try again with the trace up a bit.
1669				TracePoint[2] += 30;
1670				trap_Trace(&tr, TracePoint, ent->r.mins, ent->r.maxs, TracePoint, 
1671					ent->s.number, ent->clipmask);
1672				
1673				if(tr.startsolid)
1674				{//hmm, well that didn't work. try one last time with the item back 
1675					//away from where the dude was facing (in case the carrier was
1676					//close to something they were attacking.)
1677					vec3_t fwd;
1678					if(targ->client)
1679					{
1680						AngleVectors(targ->client->ps.viewangles,fwd, NULL, NULL);
1681					}
1682					else
1683					{
1684						AngleVectors(targ->r.currentAngles,fwd, NULL, NULL);
1685					}
1686					VectorMA(TracePoint, -30, fwd, TracePoint);
1687					trap_Trace(&tr, TracePoint, ent->r.mins, ent->r.maxs, TracePoint, ent->s.number, ent->clipmask);
1688					
1689					if(tr.startsolid)
1690					{//crap, that's all we got.  just spawn at the defualt location.
1691						return;
1692					}
1693				}
1694			}
1695			G_SetOrigin(ent, TracePoint);
1696			//G_SetOrigin(ent, targ->r.currentOrigin);
1697			trap_LinkEntity(ent);
1698		}
1699	}
1700}
1701
1702/*QUAKED misc_siege_item (1 0 1) (-16 -16 -24) (16 16 32) ? x x STARTOFFRADAR
1703STARTOFFRADAR - start not displaying on radar, don't display until used.
1704
1705"model"				Name of model to use for the object
1706"mins"				Actual mins of the object. Careful not to place it into a solid,
1707					as these new mins will not be reflected visually in the editor.
1708					Default value is "-16 -16 -24".
1709"maxs"				Same as above for maxs. Default value is "16 16 32".
1710"targetname"		If it has a targetname, it will only spawn upon being used.
1711"target2"			Target to fire upon pickup. If none, nothing will happen.
1712"pickuponlyonce"	If non-0, target2 will only be fired on the first pickup. If the item is
1713					dropped and picked up again later, the target will not be fired off on
1714					the sequential pickup. Default value is 1.
1715"target3"			Target to fire upon delivery of the item to the goal point.
1716					If none, nothing will happen. (but you should always want something to happen)
1717"health"			If > 0, object can be damaged and will die once health reaches 0. Default is 0.
1718"showhealth"		if health > 0, will show a health meter for this item
1719"teamowner"			Which team owns this item, used only for deciding what color to make health meter
1720"target4"			Target to fire upon death, if damageable. Default is none.
1721"deathfx"			Effect to play on death, if damageable. Default is none.
1722"canpickup"			If non-0, item can be picked up. Otherwise it will just be solid and sit on the
1723					ground. Default is 1.
1724"pickupsound"		Sound to play on pickup, if any.
1725"goaltarget"		Must be the targetname of a trigger_multi/trigger_once. Once a player carrying
1726					this object is brought inside the specified trigger, then that trigger will be
1727					allowed to fire. Ideally it will target a siege objective or something like that.
1728"usephysics"		If non-0, run standard physics on the object. Default is 1.
1729"mass"				If usephysics, this will be the factored object mass. Default is 0.09.
1730"gravity"			If usephysics, this will be the factored gravitational pull. Default is 3.0.
1731"bounce"			If usephysics, this will be the factored bounce amount. Default is 1.3.
1732"teamnotouch"		If 1 don't let team 1 pickup, if 2 don't let team 2. By default both teams
1733					can pick this object up and carry it around until death.
1734"teamnocomplete"	Same values as above, but controls if this object can be taken into the objective
1735					area by said team.
1736"respawnfx"			Plays this effect when respawning (e.g. it is left in an unknown area too long
1737					and goes back to the original spot). If this is not specified there will be
1738					no effect. (expected format is .efx file)
1739"paintarget"		plop self on top of this guy's origin when we are used (only applies if the siege
1740					item has a targetname)
1741"noradar"			if non-0 this thing will not show up on radar
1742
1743"forcelimit"		if non-0, while carrying this item, the carrier's force powers will be crippled.
1744"target5"			target to fire when respawning.
1745"target6"			target to fire when dropped by someone carrying this item.
1746
1747"icon"				icon that represents the gametype item on the radar
1748
1749health charge things only work with showhealth 1 on siege items that take damage.
1750"health_chargeamt"	if non-0 will recharge this much health every...
1751"health_chargerate"	...this many milliseconds
1752*/
1753void SP_misc_siege_item (gentity_t *ent)
1754{
1755	int		canpickup;
1756	int		noradar;
1757	char	*s;
1758
1759	if (!siege_valid || level.gametype != GT_SIEGE)
1760	{
1761		G_FreeEntity(ent);
1762		return;
1763	}
1764
1765	if (!ent->model || !ent->model[0])
1766	{
1767		G_Error("You must specify a model for misc_siege_item types.");
1768	}
1769
1770	G_SpawnInt("canpickup", "1", &canpickup);
1771	G_SpawnInt("usephysics", "1", &ent->genericValue1);
1772
1773	if (ent->genericValue1)
1774	{ //if we're using physics we want lerporigin smoothing
1775		ent->s.eFlags |= EF_CLIENTSMOOTH;
1776	}
1777
1778	G_SpawnInt("noradar", "0", &noradar);
1779	//Want it to always show up as a goal object on radar
1780	if (!noradar && !(ent->spawnflags & SIEGEITEM_STARTOFFRADAR))
1781	{
1782		ent->s.eFlags |= EF_RADAROBJECT;
1783	}
1784
1785	//All clients want to know where it is at all times for radar
1786	ent->r.svFlags |= SVF_BROADCAST;
1787
1788	G_SpawnInt("pickuponlyonce", "1", &ent->genericValue4);
1789
1790	G_SpawnInt("teamnotouch", "0", &ent->genericValue6);
1791	G_SpawnInt("teamnocomplete", "0", &ent->genericValue7);
1792	
1793	//Get default physics values.
1794	G_SpawnFloat("mass", "0.09", &ent->mass);
1795	G_SpawnFloat("gravity", "3.0", &ent->radius);
1796	G_SpawnFloat("bounce", "1.3", &ent->random);
1797
1798	G_SpawnString( "pickupsound", "", &s );
1799
1800	if (s && s[0])
1801	{ //We have a pickup sound, so index it now.
1802		ent->noise_index = G_SoundIndex(s);
1803	}
1804
1805	G_SpawnString( "deathfx", "", &s );
1806
1807	if (s && s[0])
1808	{ //We have a death effect, so index it now.
1809		ent->genericValue3 = G_EffectIndex(s);
1810	}
1811
1812	G_SpawnString( "respawnfx", "", &s );
1813
1814	if (s && s[0])
1815	{ //We have a respawn effect, so index it now.
1816		ent->genericValue10 = G_EffectIndex(s);
1817	}
1818
1819	G_SpawnString( "icon", "", &s );
1820	
1821	if (s && s[0])
1822	{ 
1823		// We have an icon, so index it now.  We are reusing the genericenemyindex
1824		// variable rather than adding a new one to the entity state.
1825		ent->s.genericenemyindex = G_IconIndex(s);
1826	}
1827	
1828	ent->s.modelindex = G_ModelIndex(ent->model);
1829
1830	//Is the model a ghoul2 model?
1831	if ( ent->model && !Q_stricmp( &ent->model[strlen(ent->model) - 4], ".glm" ) )
1832	{ //apparently so.
1833        ent->s.modelGhoul2 = 1;
1834	}
1835
1836	ent->s.eType = ET_GENERAL;
1837
1838	//Set the mins/maxs with default values.
1839	G_SpawnVector("mins", "-16 -16 -24", ent->r.mins);
1840	G_SpawnVector("maxs", "16 16 32", ent->r.maxs);
1841
1842	VectorCopy(ent->s.origin, ent->pos1); //store off the initial origin for respawning
1843	G_SetOrigin(ent, ent->s.origin);
1844
1845	VectorCopy(ent->s.angles, ent->r.currentAngles);
1846	VectorCopy(ent->s.angles, ent->s.apos.trBase);
1847
1848	G_SpawnInt("forcelimit", "0", &ent->genericValue15);
1849
1850	if (ent->health > 0)
1851	{ //If it has health, it can be killed.
1852		int t;
1853
1854		ent->pain = SiegeItemPain;
1855		ent->die = SiegeItemDie;
1856		ent->takedamage = qtrue;
1857
1858		G_SpawnInt( "showhealth", "0", &t );
1859		if (t)
1860		{ //a non-0 maxhealth value will mean we want to show the health on the hud
1861			ent->maxHealth = ent->health;
1862			G_ScaleNetHealth(ent);
1863
1864			G_SpawnInt( "health_chargeamt", "0", &ent->genericValue12);
1865			G_SpawnInt( "health_chargerate", "0", &ent->genericValue13);
1866		}
1867	}
1868	else
1869	{ //Otherwise no.
1870		ent->takedamage = qfalse;
1871	}
1872
1873	if (ent->spawnflags & SIEGEITEM_STARTOFFRADAR)
1874	{
1875		ent->use = SiegeItemUse;
1876	}
1877	else if (ent->targetname && ent->targetname[0])
1878	{
1879		ent->s.eFlags |= EF_NODRAW; //kind of hacky, but whatever
1880		ent->genericValue11 = canpickup;
1881        ent->use = SiegeItemUse;
1882		ent->s.eFlags &= ~EF_RADAROBJECT;
1883	}
1884
1885	if ( (!ent->targetname || !ent->targetname[0]) ||
1886		 (ent->spawnflags & SIEGEITEM_STARTOFFRADAR) )
1887	{
1888		if (canpickup || !ent->takedamage)
1889		{ //We want to be able to walk into it to pick it up then.
1890			ent->r.contents = CONTENTS_TRIGGER;
1891			ent->clipmask = CONTENTS_SOLID|CONTENTS_TERRAIN;
1892			if (canpickup)
1893			{
1894				ent->touch = SiegeItemTouch;
1895			}
1896		}
1897		else
1898		{ //Make it solid.
1899			ent->r.contents = MASK_PLAYERSOLID;
1900			ent->clipmask = MASK_PLAYERSOLID;
1901		}
1902
1903		ent->think = SiegeItemThink;
1904		ent->nextthink = level.time + FRAMETIME/2;
1905	}
1906
1907	ent->genericValue8 = ENTITYNUM_NONE; //initialize the carrier to none
1908
1909	ent->neverFree = qtrue; //never free us unless we specifically request it.
1910
1911	trap_LinkEntity(ent);
1912}
1913
1914//sends extra data about other client's in this client's PVS
1915//used for support guy etc.
1916//with this formatting:
1917//sxd 16,999,999,999|17,999,999,999
1918//assumed max 2 chars for cl num, 3 chars per ammo/health/maxhealth, even a single string full of
1919//info for all 32 clients should not get much past 450 bytes, which is well within a
1920//reasonable range. We don't need to send anything about the max ammo or current weapon, because
1921//currentState.weapon can be checked for the ent in question on the client. -rww
1922void G_SiegeClientExData(gentity_t *msgTarg)
1923{
1924	gentity_t *ent;
1925	int count = 0;
1926	int i = 0;
1927	char str[MAX_STRING_CHARS];
1928	char scratch[MAX_STRING_CHARS];
1929
1930	while (i < level.num_entities && count < MAX_EXDATA_ENTS_TO_SEND)
1931	{
1932		ent = &g_entities[i];
1933
1934		if (ent->inuse && ent->client && msgTarg->s.number != ent->s.number &&
1935			ent->s.eType == ET_PLAYER && msgTarg->client->sess.sessionTeam == ent->client->sess.sessionTeam &&
1936			trap_InPVS(msgTarg->client->ps.origin, ent->client->ps.origin))
1937		{ //another client in the same pvs, send his jive
1938            if (count)
1939			{ //append a seperating space if we are not the first in the list
1940				Q_strcat(str, sizeof(str), " ");
1941			}
1942			else
1943			{ //otherwise create the prepended chunk
1944				strcpy(str, "sxd ");
1945			}
1946
1947			//append the stats
1948			Com_sprintf(scratch, sizeof(scratch), "%i|%i|%i|%i", ent->s.number, ent->client->ps.stats[STAT_HEALTH],
1949				ent->client->ps.stats[STAT_MAX_HEALTH], ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex]);
1950			Q_strcat(str, sizeof(str), scratch);
1951			count++;
1952		}
1953		i++;
1954	}
1955
1956	if (!count)
1957	{ //nothing to send
1958		return;
1959	}
1960
1961	//send the string to him
1962	trap_SendServerCommand(msgTarg-g_entities, str);
1963}