1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 #include "ghoul2/G2.h"
26 #include "qcommon/q_shared.h"
27 
28 /*
29 
30   Items are any object that a player can touch to gain some effect.
31 
32   Pickup will return the number of seconds until they should respawn.
33 
34   all items should pop when dropped in lava or slime
35 
36   Respawnable items don't actually go away when picked up, they are
37   just made invisible and untouchable.  This allows them to ride
38   movers and respawn appropriately.
39 */
40 
41 
42 #define	RESPAWN_ARMOR		20
43 #define	RESPAWN_TEAM_WEAPON	30
44 #define	RESPAWN_HEALTH		30
45 #define	RESPAWN_AMMO		40
46 #define	RESPAWN_HOLDABLE	60
47 #define	RESPAWN_MEGAHEALTH	120
48 #define	RESPAWN_POWERUP		120
49 
50 // Item Spawn flags
51 #define ITMSF_SUSPEND		1
52 #define ITMSF_NOPLAYER		2
53 #define ITMSF_ALLOWNPC		4
54 #define ITMSF_NOTSOLID		8
55 #define ITMSF_VERTICAL		16
56 #define ITMSF_INVISIBLE		32
57 
58 extern gentity_t *droppedRedFlag;
59 extern gentity_t *droppedBlueFlag;
60 
61 
62 //======================================================================
63 #define MAX_MEDPACK_HEAL_AMOUNT		25
64 #define MAX_MEDPACK_BIG_HEAL_AMOUNT	50
65 #define MAX_SENTRY_DISTANCE			256
66 
67 // For more than four players, adjust the respawn times, up to 1/4.
adjustRespawnTime(float preRespawnTime,int itemType,int itemTag)68 int adjustRespawnTime(float preRespawnTime, int itemType, int itemTag)
69 {
70 	float respawnTime = preRespawnTime;
71 
72 	if (itemType == IT_WEAPON)
73 	{
74 		if (itemTag == WP_THERMAL ||
75 			itemTag == WP_TRIP_MINE ||
76 			itemTag == WP_DET_PACK)
77 		{ //special case for these, use ammo respawn rate
78 			respawnTime = RESPAWN_AMMO;
79 		}
80 	}
81 
82 	if (!g_adaptRespawn.integer)
83 	{
84 		return((int)respawnTime);
85 	}
86 
87 	if (level.numPlayingClients > 4)
88 	{	// Start scaling the respawn times.
89 		if (level.numPlayingClients > 32)
90 		{	// 1/4 time minimum.
91 			respawnTime *= 0.25;
92 		}
93 		else if (level.numPlayingClients > 12)
94 		{	// From 12-32, scale from 0.5 to 0.25;
95 			respawnTime *= 20.0 / (float)(level.numPlayingClients + 8);
96 		}
97 		else
98 		{	// From 4-12, scale from 1.0 to 0.5;
99 			respawnTime *= 8.0 / (float)(level.numPlayingClients + 4);
100 		}
101 	}
102 
103 	if (respawnTime < 1.0)
104 	{	// No matter what, don't go lower than 1 second, or the pickups become very noisy!
105 		respawnTime = 1.0;
106 	}
107 
108 	return ((int)respawnTime);
109 }
110 
111 
112 #define SHIELD_HEALTH				250
113 #define SHIELD_HEALTH_DEC			10		// 25 seconds
114 #define MAX_SHIELD_HEIGHT			254
115 #define MAX_SHIELD_HALFWIDTH		255
116 #define SHIELD_HALFTHICKNESS		4
117 #define SHIELD_PLACEDIST			64
118 
119 #define SHIELD_SIEGE_HEALTH			2000
120 #define SHIELD_SIEGE_HEALTH_DEC		(SHIELD_SIEGE_HEALTH/25)	// still 25 seconds.
121 
122 static qhandle_t	shieldLoopSound=0;
123 static qhandle_t	shieldAttachSound=0;
124 static qhandle_t	shieldActivateSound=0;
125 static qhandle_t	shieldDeactivateSound=0;
126 static qhandle_t	shieldDamageSound=0;
127 
128 
ShieldRemove(gentity_t * self)129 void ShieldRemove(gentity_t *self)
130 {
131 	self->think = G_FreeEntity;
132 	self->nextthink = level.time + 100;
133 
134 	// Play kill sound...
135 	G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
136 	self->s.loopSound = 0;
137 	self->s.loopIsSoundset = qfalse;
138 
139 	return;
140 }
141 
142 
143 // Count down the health of the shield.
ShieldThink(gentity_t * self)144 void ShieldThink(gentity_t *self)
145 {
146 	self->s.trickedentindex = 0;
147 
148 	if ( level.gametype == GT_SIEGE )
149 	{
150 		self->health -= SHIELD_SIEGE_HEALTH_DEC;
151 	}
152 	else
153 	{
154 		self->health -= SHIELD_HEALTH_DEC;
155 	}
156 	self->nextthink = level.time + 1000;
157 	if (self->health <= 0)
158 	{
159 		ShieldRemove(self);
160 	}
161 	return;
162 }
163 
164 
165 // The shield was damaged to below zero health.
ShieldDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)166 void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
167 {
168 	// Play damaging sound...
169 	G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
170 
171 	ShieldRemove(self);
172 }
173 
174 
175 // The shield had damage done to it.  Make it flicker.
ShieldPain(gentity_t * self,gentity_t * attacker,int damage)176 void ShieldPain(gentity_t *self, gentity_t *attacker, int damage)
177 {
178 	// Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn.
179 	self->think = ShieldThink;
180 	self->nextthink = level.time + 400;
181 
182 	// Play damaging sound...
183 	G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
184 
185 	self->s.trickedentindex = 1;
186 
187 	return;
188 }
189 
190 
191 // Try to turn the shield back on after a delay.
ShieldGoSolid(gentity_t * self)192 void ShieldGoSolid(gentity_t *self)
193 {
194 	trace_t		tr;
195 
196 	// see if we're valid
197 	self->health--;
198 	if (self->health <= 0)
199 	{
200 		ShieldRemove(self);
201 		return;
202 	}
203 
204 	trap->Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY, qfalse, 0, 0 );
205 	if(tr.startsolid)
206 	{	// gah, we can't activate yet
207 		self->nextthink = level.time + 200;
208 		self->think = ShieldGoSolid;
209 		trap->LinkEntity((sharedEntity_t *)self);
210 	}
211 	else
212 	{ // get hard... huh-huh...
213 		self->s.eFlags &= ~EF_NODRAW;
214 
215 		self->r.contents = CONTENTS_SOLID;
216 		self->nextthink = level.time + 1000;
217 		self->think = ShieldThink;
218 		self->takedamage = qtrue;
219 		trap->LinkEntity((sharedEntity_t *)self);
220 
221 		// Play raising sound...
222 		G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
223 		self->s.loopSound = shieldLoopSound;
224 		self->s.loopIsSoundset = qfalse;
225 	}
226 
227 	return;
228 }
229 
230 
231 // Turn the shield off to allow a friend to pass through.
ShieldGoNotSolid(gentity_t * self)232 void ShieldGoNotSolid(gentity_t *self)
233 {
234 	// make the shield non-solid very briefly
235 	self->r.contents = 0;
236 	self->s.eFlags |= EF_NODRAW;
237 	// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
238 	self->nextthink = level.time + 200;
239 	self->think = ShieldGoSolid;
240 	self->takedamage = qfalse;
241 	trap->LinkEntity((sharedEntity_t *)self);
242 
243 	// Play kill sound...
244 	G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
245 	self->s.loopSound = 0;
246 	self->s.loopIsSoundset = qfalse;
247 }
248 
249 
250 // Somebody (a player) has touched the shield.  See if it is a "friend".
ShieldTouch(gentity_t * self,gentity_t * other,trace_t * trace)251 void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace)
252 {
253 	if (level.gametype >= GT_TEAM)
254 	{ // let teammates through
255 		// compare the parent's team to the "other's" team
256 		if (self->parent && ( self->parent->client) && (other->client))
257 		{
258 			if (OnSameTeam(self->parent, other))
259 			{
260 				ShieldGoNotSolid(self);
261 			}
262 		}
263 	}
264 	else
265 	{//let the person who dropped the shield through
266 		if (self->parent && self->parent->s.number == other->s.number)
267 		{
268 			ShieldGoNotSolid(self);
269 		}
270 	}
271 }
272 
273 
274 // After a short delay, create the shield by expanding in all directions.
CreateShield(gentity_t * ent)275 void CreateShield(gentity_t *ent)
276 {
277 	trace_t		tr;
278 	vec3_t		mins, maxs, end, posTraceEnd, negTraceEnd, start;
279 	int			height, posWidth, negWidth, halfWidth = 0;
280 	qboolean	xaxis;
281 	int			paramData = 0;
282 //	static int	shieldID;
283 
284 	// trace upward to find height of shield
285 	VectorCopy(ent->r.currentOrigin, end);
286 	end[2] += MAX_SHIELD_HEIGHT;
287 	trap->Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
288 	height = (int)(MAX_SHIELD_HEIGHT * tr.fraction);
289 
290 	// use angles to find the proper axis along which to align the shield
291 	VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0);
292 	VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height);
293 	VectorCopy(ent->r.currentOrigin, posTraceEnd);
294 	VectorCopy(ent->r.currentOrigin, negTraceEnd);
295 
296 	if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis
297 	{
298 		posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
299 		negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
300 		xaxis = qfalse;
301 	}
302 	else  // shield runs along x-axis
303 	{
304 		posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
305 		negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
306 		xaxis = qtrue;
307 	}
308 
309 	// trace horizontally to find extend of shield
310 	// positive trace
311 	VectorCopy(ent->r.currentOrigin, start);
312 	start[2] += (height>>1);
313 	trap->Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
314 	posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
315 	// negative trace
316 	trap->Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
317 	negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
318 
319 	// kef -- monkey with dimensions and place origin in center
320 	halfWidth = (posWidth + negWidth)>>1;
321 	if (xaxis)
322 	{
323 		ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth;
324 	}
325 	else
326 	{
327 		ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth;
328 	}
329 	ent->r.currentOrigin[2] += (height>>1);
330 
331 	// set entity's mins and maxs to new values, make it solid, and link it
332 	if (xaxis)
333 	{
334 		VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1));
335 		VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1);
336 	}
337 	else
338 	{
339 		VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1));
340 		VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height);
341 	}
342 	ent->clipmask = MASK_SHOT;
343 
344 	// Information for shield rendering.
345 
346 //	xaxis - 1 bit
347 //	height - 0-254 8 bits
348 //	posWidth - 0-255 8 bits
349 //  negWidth - 0 - 255 8 bits
350 
351 	paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth);
352 	ent->s.time2 = paramData;
353 
354 	if ( level.gametype == GT_SIEGE )
355 	{
356 		ent->health = ceil((float)(SHIELD_SIEGE_HEALTH*1));
357 	}
358 	else
359 	{
360 		ent->health = ceil((float)(SHIELD_HEALTH*1));
361 	}
362 
363 	ent->s.time = ent->health;//???
364 	ent->pain = ShieldPain;
365 	ent->die = ShieldDie;
366 	ent->touch = ShieldTouch;
367 
368 	// see if we're valid
369 	trap->Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY, qfalse, 0, 0 );
370 
371 	if (tr.startsolid)
372 	{	// Something in the way!
373 		// make the shield non-solid very briefly
374 		ent->r.contents = 0;
375 		ent->s.eFlags |= EF_NODRAW;
376 		// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
377 		ent->nextthink = level.time + 200;
378 		ent->think = ShieldGoSolid;
379 		ent->takedamage = qfalse;
380 		trap->LinkEntity((sharedEntity_t *)ent);
381 	}
382 	else
383 	{	// Get solid.
384 		ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID;
385 
386 		ent->nextthink = level.time;
387 		ent->think = ShieldThink;
388 
389 		ent->takedamage = qtrue;
390 		trap->LinkEntity((sharedEntity_t *)ent);
391 
392 		// Play raising sound...
393 		G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
394 		ent->s.loopSound = shieldLoopSound;
395 		ent->s.loopIsSoundset = qfalse;
396 	}
397 
398 	ShieldGoSolid(ent);
399 
400 	return;
401 }
402 
PlaceShield(gentity_t * playerent)403 qboolean PlaceShield(gentity_t *playerent)
404 {
405 	static const gitem_t *shieldItem = NULL;
406 	gentity_t	*shield = NULL;
407 	trace_t		tr;
408 	vec3_t		fwd, pos, dest, mins = {-4,-4, 0}, maxs = {4,4,4};
409 	static qboolean registered = qfalse;
410 
411 	if ( !registered )
412 	{
413 		shieldLoopSound = G_SoundIndex("sound/movers/doors/forcefield_lp.wav");
414 		shieldAttachSound = G_SoundIndex("sound/weapons/detpack/stick.wav");
415 		shieldActivateSound = G_SoundIndex("sound/movers/doors/forcefield_on.wav");
416 		shieldDeactivateSound = G_SoundIndex("sound/movers/doors/forcefield_off.wav");
417 		shieldDamageSound = G_SoundIndex("sound/effects/bumpfield.wav");
418 		shieldItem = BG_FindItemForHoldable(HI_SHIELD);
419 		registered = qtrue;
420 	}
421 
422 	// can we place this in front of us?
423 	AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL);
424 	fwd[2] = 0;
425 	VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest);
426 	trap->Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT, qfalse, 0, 0 );
427 	if (tr.fraction > 0.9)
428 	{//room in front
429 		VectorCopy(tr.endpos, pos);
430 		// drop to floor
431 		VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
432 		trap->Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID, qfalse, 0, 0 );
433 		if ( !tr.startsolid && !tr.allsolid )
434 		{
435 			// got enough room so place the portable shield
436 			shield = G_Spawn();
437 
438 			// Figure out what direction the shield is facing.
439 			if (fabs(fwd[0]) > fabs(fwd[1]))
440 			{	// shield is north/south, facing east.
441 				shield->s.angles[YAW] = 0;
442 			}
443 			else
444 			{	// shield is along the east/west axis, facing north
445 				shield->s.angles[YAW] = 90;
446 			}
447 			shield->think = CreateShield;
448 			shield->nextthink = level.time + 500;	// power up after .5 seconds
449 			shield->parent = playerent;
450 
451 			// Set team number.
452 			shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam;
453 
454 			shield->s.eType = ET_SPECIAL;
455 			shield->s.modelindex =  HI_SHIELD;	// this'll be used in CG_Useable() for rendering.
456 			shield->classname = shieldItem->classname;
457 
458 			shield->r.contents = CONTENTS_TRIGGER;
459 
460 			shield->touch = 0;
461 			// using an item causes it to respawn
462 			shield->use = 0; //Use_Item;
463 
464 			// allow to ride movers
465 			shield->s.groundEntityNum = tr.entityNum;
466 
467 			G_SetOrigin( shield, tr.endpos );
468 
469 			shield->s.eFlags &= ~EF_NODRAW;
470 			shield->r.svFlags &= ~SVF_NOCLIENT;
471 
472 			trap->LinkEntity ((sharedEntity_t *)shield);
473 
474 			shield->s.owner = playerent->s.number;
475 			shield->s.shouldtarget = qtrue;
476 			if (level.gametype >= GT_TEAM)
477 			{
478 				shield->s.teamowner = playerent->client->sess.sessionTeam;
479 			}
480 			else
481 			{
482 				shield->s.teamowner = 16;
483 			}
484 
485 			// Play placing sound...
486 			G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound);
487 
488 			return qtrue;
489 		}
490 	}
491 	// no room
492 	return qfalse;
493 }
494 
ItemUse_Binoculars(gentity_t * ent)495 void ItemUse_Binoculars(gentity_t *ent)
496 {
497 	if (!ent || !ent->client)
498 	{
499 		return;
500 	}
501 
502 	if (ent->client->ps.weaponstate != WEAPON_READY)
503 	{ //So we can't fool it and reactivate while switching to the saber or something.
504 		return;
505 	}
506 
507 	/*
508 	if (ent->client->ps.weapon == WP_SABER)
509 	{ //No.
510 		return;
511 	}
512 	*/
513 
514 	if (ent->client->ps.zoomMode == 0) // not zoomed or currently zoomed with the disruptor
515 	{
516 		ent->client->ps.zoomMode = 2;
517 		ent->client->ps.zoomLocked = qfalse;
518 		ent->client->ps.zoomFov = 40.0f;
519 	}
520 	else if (ent->client->ps.zoomMode == 2)
521 	{
522 		ent->client->ps.zoomMode = 0;
523 		ent->client->ps.zoomTime = level.time;
524 	}
525 }
526 
ItemUse_Shield(gentity_t * ent)527 void ItemUse_Shield(gentity_t *ent)
528 {
529 	PlaceShield(ent);
530 }
531 
532 //--------------------------
533 // PERSONAL ASSAULT SENTRY
534 //--------------------------
535 
536 #define PAS_DAMAGE	2
537 
SentryTouch(gentity_t * ent,gentity_t * other,trace_t * trace)538 void SentryTouch(gentity_t *ent, gentity_t *other, trace_t *trace)
539 {
540 	return;
541 }
542 
543 //----------------------------------------------------------------
pas_fire(gentity_t * ent)544 void pas_fire( gentity_t *ent )
545 //----------------------------------------------------------------
546 {
547 	vec3_t fwd, myOrg, enOrg;
548 
549 	VectorCopy(ent->r.currentOrigin, myOrg);
550 	myOrg[2] += 24;
551 
552 	VectorCopy(ent->enemy->client->ps.origin, enOrg);
553 	enOrg[2] += 24;
554 
555 	VectorSubtract(enOrg, myOrg, fwd);
556 	VectorNormalize(fwd);
557 
558 	myOrg[0] += fwd[0]*16;
559 	myOrg[1] += fwd[1]*16;
560 	myOrg[2] += fwd[2]*16;
561 
562 	WP_FireTurretMissile(&g_entities[ent->genericValue3], myOrg, fwd, qfalse, 10, 2300, MOD_SENTRY, ent );
563 
564 	G_RunObject(ent);
565 }
566 
567 #define TURRET_RADIUS 800
568 
569 //-----------------------------------------------------
pas_find_enemies(gentity_t * self)570 static qboolean pas_find_enemies( gentity_t *self )
571 //-----------------------------------------------------
572 {
573 	qboolean	found = qfalse;
574 	int			count, i;
575 	float		bestDist = TURRET_RADIUS*TURRET_RADIUS;
576 	float		enemyDist;
577 	vec3_t		enemyDir, org, org2;
578 	gentity_t	*entity_list[MAX_GENTITIES], *target;
579 	trace_t		tr;
580 
581 	if ( self->aimDebounceTime > level.time ) // time since we've been shut off
582 	{
583 		// We were active and alert, i.e. had an enemy in the last 3 secs
584 		if ( self->painDebounceTime < level.time )
585 		{
586 			G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
587 			self->painDebounceTime = level.time + 1000;
588 		}
589 	}
590 
591 	VectorCopy(self->s.pos.trBase, org2);
592 
593 	count = G_RadiusList( org2, TURRET_RADIUS, self, qtrue, entity_list );
594 
595 	for ( i = 0; i < count; i++ )
596 	{
597 		target = entity_list[i];
598 
599 		if ( !target->client )
600 		{
601 			continue;
602 		}
603 		if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
604 		{
605 			continue;
606 		}
607 		if ( self->alliedTeam && target->client->sess.sessionTeam == self->alliedTeam )
608 		{
609 			continue;
610 		}
611 		if (self->genericValue3 == target->s.number)
612 		{
613 			continue;
614 		}
615 		if ( !trap->InPVS( org2, target->r.currentOrigin ))
616 		{
617 			continue;
618 		}
619 
620 		if (target->s.eType == ET_NPC &&
621 			target->s.NPC_class == CLASS_VEHICLE)
622 		{ //don't get mad at vehicles, silly.
623 			continue;
624 		}
625 
626 		if ( target->client )
627 		{
628 			VectorCopy( target->client->ps.origin, org );
629 		}
630 		else
631 		{
632 			VectorCopy( target->r.currentOrigin, org );
633 		}
634 
635 		trap->Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, qfalse, 0, 0 );
636 
637 		if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
638 		{
639 			// Only acquire if have a clear shot, Is it in range and closer than our best?
640 			VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir );
641 			enemyDist = VectorLengthSquared( enemyDir );
642 
643 			if ( enemyDist < bestDist )// all things equal, keep current
644 			{
645 				if ( self->attackDebounceTime + 100 < level.time )
646 				{
647 					// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
648 					G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
649 
650 					// Wind up turrets for a bit
651 					self->attackDebounceTime = level.time + 900 + Q_flrand(0.0f, 1.0f) * 200;
652 				}
653 
654 				G_SetEnemy( self, target );
655 				bestDist = enemyDist;
656 				found = qtrue;
657 			}
658 		}
659 	}
660 
661 	return found;
662 }
663 
664 //---------------------------------
pas_adjust_enemy(gentity_t * ent)665 void pas_adjust_enemy( gentity_t *ent )
666 //---------------------------------
667 {
668 	trace_t	tr;
669 	qboolean keep = qtrue;
670 
671 	if ( ent->enemy->health <= 0 )
672 	{
673 		keep = qfalse;
674 	}
675 	else
676 	{
677 		vec3_t		org, org2;
678 
679 		VectorCopy(ent->s.pos.trBase, org2);
680 
681 		if ( ent->enemy->client )
682 		{
683 			VectorCopy( ent->enemy->client->ps.origin, org );
684 			org[2] -= 15;
685 		}
686 		else
687 		{
688 			VectorCopy( ent->enemy->r.currentOrigin, org );
689 		}
690 
691 		trap->Trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
692 
693 		if ( tr.allsolid || tr.startsolid || tr.fraction < 0.9f || tr.entityNum == ent->s.number )
694 		{
695 			if (tr.entityNum != ent->enemy->s.number)
696 			{
697 				// trace failed
698 				keep = qfalse;
699 			}
700 		}
701 	}
702 
703 	if ( keep )
704 	{
705 		//ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
706 	}
707 	else if ( ent->bounceCount < level.time && ent->enemy ) // don't ping pong on and off
708 	{
709 		ent->enemy = NULL;
710 		// shut-down sound
711 		G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
712 
713 		ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
714 
715 		// make turret play ping sound for 5 seconds
716 		ent->aimDebounceTime = level.time + 5000;
717 	}
718 }
719 
720 #define TURRET_DEATH_DELAY 2000
721 #define TURRET_LIFETIME 60000
722 
723 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
724 
sentryExpire(gentity_t * self)725 void sentryExpire(gentity_t *self)
726 {
727 	turret_die(self, self, self, 1000, MOD_UNKNOWN);
728 }
729 
730 //---------------------------------
pas_think(gentity_t * ent)731 void pas_think( gentity_t *ent )
732 //---------------------------------
733 {
734 	qboolean	moved;
735 	float		diffYaw, diffPitch;
736 	vec3_t		enemyDir, org;
737 	vec3_t		frontAngles, backAngles;
738 	vec3_t		desiredAngles;
739 	int			iEntityList[MAX_GENTITIES];
740 	int			numListedEntities;
741 	int			i = 0;
742 	qboolean	clTrapped = qfalse;
743 	vec3_t		testMins, testMaxs;
744 
745 	testMins[0] = ent->r.currentOrigin[0] + ent->r.mins[0]+4;
746 	testMins[1] = ent->r.currentOrigin[1] + ent->r.mins[1]+4;
747 	testMins[2] = ent->r.currentOrigin[2] + ent->r.mins[2]+4;
748 
749 	testMaxs[0] = ent->r.currentOrigin[0] + ent->r.maxs[0]-4;
750 	testMaxs[1] = ent->r.currentOrigin[1] + ent->r.maxs[1]-4;
751 	testMaxs[2] = ent->r.currentOrigin[2] + ent->r.maxs[2]-4;
752 
753 	numListedEntities = trap->EntitiesInBox( testMins, testMaxs, iEntityList, MAX_GENTITIES );
754 
755 	while (i < numListedEntities)
756 	{
757 		if (iEntityList[i] < MAX_CLIENTS)
758 		{ //client stuck inside me. go nonsolid.
759 			int clNum = iEntityList[i];
760 
761 			numListedEntities = trap->EntitiesInBox( g_entities[clNum].r.absmin, g_entities[clNum].r.absmax, iEntityList, MAX_GENTITIES );
762 
763 			i = 0;
764 			while (i < numListedEntities)
765 			{
766 				if (iEntityList[i] == ent->s.number)
767 				{
768 					clTrapped = qtrue;
769 					break;
770 				}
771 				i++;
772 			}
773 			break;
774 		}
775 
776 		i++;
777 	}
778 
779 	if (clTrapped)
780 	{
781 		ent->r.contents = 0;
782 		ent->s.fireflag = 0;
783 		ent->nextthink = level.time + FRAMETIME;
784 		return;
785 	}
786 	else
787 	{
788 		ent->r.contents = CONTENTS_SOLID;
789 	}
790 
791 	if (!g_entities[ent->genericValue3].inuse || !g_entities[ent->genericValue3].client ||
792 		g_entities[ent->genericValue3].client->sess.sessionTeam != ent->genericValue2)
793 	{
794 		ent->think = G_FreeEntity;
795 		ent->nextthink = level.time;
796 		return;
797 	}
798 
799 //	G_RunObject(ent);
800 
801 	if ( !ent->damage )
802 	{
803 		ent->damage = 1;
804 		ent->nextthink = level.time + FRAMETIME;
805 		return;
806 	}
807 
808 	if ((ent->genericValue8+TURRET_LIFETIME) < level.time)
809 	{
810 		G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
811 		ent->s.bolt2 = ENTITYNUM_NONE;
812 		ent->s.fireflag = 2;
813 
814 		ent->think = sentryExpire;
815 		ent->nextthink = level.time + TURRET_DEATH_DELAY;
816 		return;
817 	}
818 
819 	ent->nextthink = level.time + FRAMETIME;
820 
821 	if ( ent->enemy )
822 	{
823 		// make sure that the enemy is still valid
824 		pas_adjust_enemy( ent );
825 	}
826 
827 	if (ent->enemy)
828 	{
829 		if (!ent->enemy->client)
830 		{
831 			ent->enemy = NULL;
832 		}
833 		else if (ent->enemy->s.number == ent->s.number)
834 		{
835 			ent->enemy = NULL;
836 		}
837 		else if (ent->enemy->health < 1)
838 		{
839 			ent->enemy = NULL;
840 		}
841 	}
842 
843 	if ( !ent->enemy )
844 	{
845 		pas_find_enemies( ent );
846 	}
847 
848 	if (ent->enemy)
849 	{
850 		ent->s.bolt2 = ent->enemy->s.number;
851 	}
852 	else
853 	{
854 		ent->s.bolt2 = ENTITYNUM_NONE;
855 	}
856 
857 	moved = qfalse;
858 	diffYaw = 0.0f; diffPitch = 0.0f;
859 
860 	ent->speed = AngleNormalize360( ent->speed );
861 	ent->random = AngleNormalize360( ent->random );
862 
863 	if ( ent->enemy )
864 	{
865 		// ...then we'll calculate what new aim adjustments we should attempt to make this frame
866 		// Aim at enemy
867 		if ( ent->enemy->client )
868 		{
869 			VectorCopy( ent->enemy->client->ps.origin, org );
870 		}
871 		else
872 		{
873 			VectorCopy( ent->enemy->r.currentOrigin, org );
874 		}
875 
876 		VectorSubtract( org, ent->r.currentOrigin, enemyDir );
877 		vectoangles( enemyDir, desiredAngles );
878 
879 		diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
880 		diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
881 	}
882 	else
883 	{
884 		// no enemy, so make us slowly sweep back and forth as if searching for a new one
885 		diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
886 	}
887 
888 	if ( fabs(diffYaw) > 0.25f )
889 	{
890 		moved = qtrue;
891 
892 		if ( fabs(diffYaw) > 10.0f )
893 		{
894 			// cap max speed
895 			ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
896 		}
897 		else
898 		{
899 			// small enough
900 			ent->speed -= diffYaw;
901 		}
902 	}
903 
904 
905 	if ( fabs(diffPitch) > 0.25f )
906 	{
907 		moved = qtrue;
908 
909 		if ( fabs(diffPitch) > 4.0f )
910 		{
911 			// cap max speed
912 			ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
913 		}
914 		else
915 		{
916 			// small enough
917 			ent->random -= diffPitch;
918 		}
919 	}
920 
921 	// the bone axes are messed up, so hence some dumbness here
922 	VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
923 	VectorSet( backAngles, 0.0f, 0.0f, ent->speed );
924 
925 	if ( moved )
926 	{
927 	//ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
928 	}
929 	else
930 	{
931 		ent->s.loopSound = 0;
932 		ent->s.loopIsSoundset = qfalse;
933 	}
934 
935 	if ( ent->enemy && ent->attackDebounceTime < level.time )
936 	{
937 		ent->count--;
938 
939 		if ( ent->count )
940 		{
941 			pas_fire( ent );
942 			ent->s.fireflag = 1;
943 			ent->attackDebounceTime = level.time + 200;
944 		}
945 		else
946 		{
947 			//ent->nextthink = 0;
948 			G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
949 			ent->s.bolt2 = ENTITYNUM_NONE;
950 			ent->s.fireflag = 2;
951 
952 			ent->think = sentryExpire;
953 			ent->nextthink = level.time + TURRET_DEATH_DELAY;
954 		}
955 	}
956 	else
957 	{
958 		ent->s.fireflag = 0;
959 	}
960 }
961 
962 //------------------------------------------------------------------------------------------------------------
turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)963 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
964 //------------------------------------------------------------------------------------------------------------
965 {
966 	// Turn off the thinking of the base & use it's targets
967 	self->think = 0;//NULL;
968 	self->use = 0;//NULL;
969 
970 	if ( self->target )
971 	{
972 		G_UseTargets( self, attacker );
973 	}
974 
975 	if (!g_entities[self->genericValue3].inuse || !g_entities[self->genericValue3].client)
976 	{
977 		G_FreeEntity(self);
978 		return;
979 	}
980 
981 	// clear my data
982 	self->die  = 0;//NULL;
983 	self->takedamage = qfalse;
984 	self->health = 0;
985 
986 	// hack the effect angle so that explode death can orient the effect properly
987 	VectorSet( self->s.angles, 0, 0, 1 );
988 
989 	G_PlayEffect(EFFECT_EXPLOSION_PAS, self->s.pos.trBase, self->s.angles);
990 	G_RadiusDamage(self->s.pos.trBase, &g_entities[self->genericValue3], 30, 256, self, self, MOD_UNKNOWN);
991 
992 	g_entities[self->genericValue3].client->ps.fd.sentryDeployed = qfalse;
993 
994 	//ExplodeDeath( self );
995 	G_FreeEntity( self );
996 }
997 
turret_free(gentity_t * self)998 void turret_free(gentity_t *self)
999 {
1000 	if (!g_entities[self->genericValue3].inuse || !g_entities[self->genericValue3].client)
1001 	{
1002 		G_FreeEntity(self);
1003 		return;
1004 	}
1005 
1006 	g_entities[self->genericValue3].client->ps.fd.sentryDeployed = qfalse;
1007 
1008 	G_FreeEntity( self );
1009 }
1010 
1011 #define TURRET_AMMO_COUNT 40
1012 
1013 //---------------------------------
SP_PAS(gentity_t * base)1014 void SP_PAS( gentity_t *base )
1015 //---------------------------------
1016 {
1017 	if ( base->count == 0 )
1018 	{
1019 		// give ammo
1020 		base->count = TURRET_AMMO_COUNT;
1021 	}
1022 
1023 	base->s.bolt1 = 1; //This is a sort of hack to indicate that this model needs special turret things done to it
1024 	base->s.bolt2 = ENTITYNUM_NONE; //store our current enemy index
1025 
1026 	base->damage = 0; // start animation flag
1027 
1028 	VectorSet( base->r.mins, -8, -8, 0 );
1029 	VectorSet( base->r.maxs, 8, 8, 24 );
1030 
1031 	G_RunObject(base);
1032 
1033 	base->think = pas_think;
1034 	base->nextthink = level.time + FRAMETIME;
1035 
1036 	if ( !base->health )
1037 	{
1038 		base->health = 50;
1039 	}
1040 
1041 	base->takedamage = qtrue;
1042 	base->die  = turret_die;
1043 
1044 	base->physicsObject = qtrue;
1045 
1046 	G_Sound( base, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
1047 }
1048 
1049 //------------------------------------------------------------------------
ItemUse_Sentry(gentity_t * ent)1050 void ItemUse_Sentry( gentity_t *ent )
1051 //------------------------------------------------------------------------
1052 {
1053 	vec3_t fwd, fwdorg;
1054 	vec3_t yawonly;
1055 	vec3_t mins, maxs;
1056 	gentity_t *sentry;
1057 
1058 	if (!ent || !ent->client)
1059 	{
1060 		return;
1061 	}
1062 
1063 	VectorSet( mins, -8, -8, 0 );
1064 	VectorSet( maxs, 8, 8, 24 );
1065 
1066 
1067 	yawonly[ROLL] = 0;
1068 	yawonly[PITCH] = 0;
1069 	yawonly[YAW] = ent->client->ps.viewangles[YAW];
1070 
1071 	AngleVectors(yawonly, fwd, NULL, NULL);
1072 
1073 	fwdorg[0] = ent->client->ps.origin[0] + fwd[0]*64;
1074 	fwdorg[1] = ent->client->ps.origin[1] + fwd[1]*64;
1075 	fwdorg[2] = ent->client->ps.origin[2] + fwd[2]*64;
1076 
1077 	sentry = G_Spawn();
1078 
1079 	sentry->classname = "sentryGun";
1080 	sentry->s.modelindex = G_ModelIndex("models/items/psgun.glm"); //replace ASAP
1081 
1082 	sentry->s.g2radius = 30.0f;
1083 	sentry->s.modelGhoul2 = 1;
1084 
1085 	G_SetOrigin(sentry, fwdorg);
1086 	sentry->parent = ent;
1087 	sentry->r.contents = CONTENTS_SOLID;
1088 	sentry->s.solid = 2;
1089 	sentry->clipmask = MASK_SOLID;
1090 	VectorCopy(mins, sentry->r.mins);
1091 	VectorCopy(maxs, sentry->r.maxs);
1092 	sentry->genericValue3 = ent->s.number;
1093 	sentry->genericValue2 = ent->client->sess.sessionTeam; //so we can remove ourself if our owner changes teams
1094 	sentry->genericValue15 = HI_SENTRY_GUN;
1095 	sentry->r.absmin[0] = sentry->s.pos.trBase[0] + sentry->r.mins[0];
1096 	sentry->r.absmin[1] = sentry->s.pos.trBase[1] + sentry->r.mins[1];
1097 	sentry->r.absmin[2] = sentry->s.pos.trBase[2] + sentry->r.mins[2];
1098 	sentry->r.absmax[0] = sentry->s.pos.trBase[0] + sentry->r.maxs[0];
1099 	sentry->r.absmax[1] = sentry->s.pos.trBase[1] + sentry->r.maxs[1];
1100 	sentry->r.absmax[2] = sentry->s.pos.trBase[2] + sentry->r.maxs[2];
1101 	sentry->s.eType = ET_GENERAL;
1102 	sentry->s.pos.trType = TR_GRAVITY;//STATIONARY;
1103 	sentry->s.pos.trTime = level.time;
1104 	sentry->touch = SentryTouch;
1105 	sentry->nextthink = level.time;
1106 	sentry->genericValue4 = ENTITYNUM_NONE; //genericValue4 used as enemy index
1107 
1108 	sentry->genericValue5 = 1000;
1109 
1110 	sentry->genericValue8 = level.time;
1111 
1112 	sentry->alliedTeam = ent->client->sess.sessionTeam;
1113 
1114 	ent->client->ps.fd.sentryDeployed = qtrue;
1115 
1116 	trap->LinkEntity((sharedEntity_t *)sentry);
1117 
1118 	sentry->s.owner = ent->s.number;
1119 	sentry->s.shouldtarget = qtrue;
1120 	if (level.gametype >= GT_TEAM)
1121 	{
1122 		sentry->s.teamowner = ent->client->sess.sessionTeam;
1123 	}
1124 	else
1125 	{
1126 		sentry->s.teamowner = 16;
1127 	}
1128 
1129 	SP_PAS( sentry );
1130 }
1131 
1132 extern gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle );
ItemUse_Seeker(gentity_t * ent)1133 void ItemUse_Seeker(gentity_t *ent)
1134 {
1135 	if ( level.gametype == GT_SIEGE && d_siegeSeekerNPC.integer )
1136 	{//actualy spawn a remote NPC
1137 		gentity_t *remote = NPC_SpawnType( ent, "remote", NULL, qfalse );
1138 		if ( remote && remote->client )
1139 		{//set it to my team
1140 			remote->s.owner = remote->r.ownerNum = ent->s.number;
1141 			remote->activator = ent;
1142 			if ( ent->client->sess.sessionTeam == TEAM_BLUE )
1143 			{
1144 				remote->client->playerTeam = NPCTEAM_PLAYER;
1145 			}
1146 			else if ( ent->client->sess.sessionTeam == TEAM_RED )
1147 			{
1148 				remote->client->playerTeam = NPCTEAM_ENEMY;
1149 			}
1150 			else
1151 			{
1152 				remote->client->playerTeam = NPCTEAM_NEUTRAL;
1153 			}
1154 		}
1155 	}
1156 	else
1157 	{
1158 		ent->client->ps.eFlags |= EF_SEEKERDRONE;
1159 		ent->client->ps.droneExistTime = level.time + 30000;
1160 		ent->client->ps.droneFireTime = level.time + 1500;
1161 	}
1162 }
1163 
MedPackGive(gentity_t * ent,int amount)1164 static void MedPackGive(gentity_t *ent, int amount)
1165 {
1166 	if (!ent || !ent->client)
1167 	{
1168 		return;
1169 	}
1170 
1171 	if (ent->health <= 0 ||
1172 		ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1173 		(ent->client->ps.eFlags & EF_DEAD))
1174 	{
1175 		return;
1176 	}
1177 
1178 	if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH])
1179 	{
1180 		return;
1181 	}
1182 
1183 	ent->health += amount;
1184 
1185 	if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1186 	{
1187 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1188 	}
1189 }
1190 
ItemUse_MedPack_Big(gentity_t * ent)1191 void ItemUse_MedPack_Big(gentity_t *ent)
1192 {
1193 	MedPackGive(ent, MAX_MEDPACK_BIG_HEAL_AMOUNT);
1194 }
1195 
ItemUse_MedPack(gentity_t * ent)1196 void ItemUse_MedPack(gentity_t *ent)
1197 {
1198 	MedPackGive(ent, MAX_MEDPACK_HEAL_AMOUNT);
1199 }
1200 
1201 #define JETPACK_TOGGLE_TIME			1000
Jetpack_Off(gentity_t * ent)1202 void Jetpack_Off(gentity_t *ent)
1203 { //create effects?
1204 	assert(ent && ent->client);
1205 
1206 	if (!ent->client->jetPackOn)
1207 	{ //aready off
1208 		return;
1209 	}
1210 
1211 	ent->client->jetPackOn = qfalse;
1212 }
1213 
Jetpack_On(gentity_t * ent)1214 void Jetpack_On(gentity_t *ent)
1215 { //create effects?
1216 	assert(ent && ent->client);
1217 
1218 	if (ent->client->jetPackOn)
1219 	{ //aready on
1220 		return;
1221 	}
1222 
1223 	if (ent->client->ps.fd.forceGripBeingGripped >= level.time)
1224 	{ //can't turn on during grip interval
1225 		return;
1226 	}
1227 
1228 	if (ent->client->ps.fallingToDeath)
1229 	{ //too late!
1230 		return;
1231 	}
1232 
1233 	G_Sound(ent, CHAN_AUTO, G_SoundIndex("sound/boba/JETON"));
1234 
1235 	ent->client->jetPackOn = qtrue;
1236 }
1237 
ItemUse_Jetpack(gentity_t * ent)1238 void ItemUse_Jetpack( gentity_t *ent )
1239 {
1240 	assert(ent && ent->client);
1241 
1242 	if (ent->client->jetPackToggleTime >= level.time)
1243 	{
1244 		return;
1245 	}
1246 
1247 	if (ent->health <= 0 ||
1248 		ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1249 		(ent->client->ps.eFlags & EF_DEAD) ||
1250 		ent->client->ps.pm_type == PM_DEAD)
1251 	{ //can't use it when dead under any circumstances.
1252 		return;
1253 	}
1254 
1255 	if (!ent->client->jetPackOn &&
1256 		ent->client->ps.jetpackFuel < 5)
1257 	{ //too low on fuel to start it up
1258 		return;
1259 	}
1260 
1261 	if (ent->client->jetPackOn)
1262 	{
1263 		Jetpack_Off(ent);
1264 	}
1265 	else
1266 	{
1267 		Jetpack_On(ent);
1268 	}
1269 
1270 	ent->client->jetPackToggleTime = level.time + JETPACK_TOGGLE_TIME;
1271 }
1272 
1273 #define CLOAK_TOGGLE_TIME			1000
1274 extern void Jedi_Cloak( gentity_t *self );
1275 extern void Jedi_Decloak( gentity_t *self );
ItemUse_UseCloak(gentity_t * ent)1276 void ItemUse_UseCloak( gentity_t *ent )
1277 {
1278 	assert(ent && ent->client);
1279 
1280 	if (ent->client->cloakToggleTime >= level.time)
1281 	{
1282 		return;
1283 	}
1284 
1285 	if (ent->health <= 0 ||
1286 		ent->client->ps.stats[STAT_HEALTH] <= 0 ||
1287 		(ent->client->ps.eFlags & EF_DEAD) ||
1288 		ent->client->ps.pm_type == PM_DEAD)
1289 	{ //can't use it when dead under any circumstances.
1290 		return;
1291 	}
1292 
1293 	if (!ent->client->ps.powerups[PW_CLOAKED] &&
1294 		ent->client->ps.cloakFuel < 5)
1295 	{ //too low on fuel to start it up
1296 		return;
1297 	}
1298 
1299 	if ( ent->client->ps.powerups[PW_CLOAKED] )
1300 	{//decloak
1301 		Jedi_Decloak( ent );
1302 	}
1303 	else
1304 	{//cloak
1305 		Jedi_Cloak( ent );
1306 	}
1307 
1308 	ent->client->cloakToggleTime = level.time + CLOAK_TOGGLE_TIME;
1309 }
1310 
1311 #define TOSSED_ITEM_STAY_PERIOD			20000
1312 #define TOSSED_ITEM_OWNER_NOTOUCH_DUR	1000
1313 
SpecialItemThink(gentity_t * ent)1314 void SpecialItemThink(gentity_t *ent)
1315 {
1316 	float gravity = 3.0f;
1317 	float mass = 0.09f;
1318 	float bounce = 1.1f;
1319 
1320 	if (ent->genericValue5 < level.time)
1321 	{
1322 		ent->think = G_FreeEntity;
1323 		ent->nextthink = level.time;
1324 		return;
1325 	}
1326 
1327 	G_RunExPhys(ent, gravity, mass, bounce, qfalse, NULL, 0);
1328 	VectorCopy(ent->r.currentOrigin, ent->s.origin);
1329 	ent->nextthink = level.time + 50;
1330 }
1331 
G_SpecialSpawnItem(gentity_t * ent,gitem_t * item)1332 void G_SpecialSpawnItem(gentity_t *ent, gitem_t *item)
1333 {
1334 	RegisterItem( item );
1335 	ent->item = item;
1336 
1337 	//go away if no one wants me
1338 	ent->genericValue5 = level.time + TOSSED_ITEM_STAY_PERIOD;
1339 	ent->think = SpecialItemThink;
1340 	ent->nextthink = level.time + 50;
1341 	ent->clipmask = MASK_SOLID;
1342 
1343 	ent->physicsBounce = 0.50;		// items are bouncy
1344 	VectorSet (ent->r.mins, -8, -8, -0);
1345 	VectorSet (ent->r.maxs, 8, 8, 16);
1346 
1347 	ent->s.eType = ET_ITEM;
1348 	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
1349 
1350 	ent->r.contents = CONTENTS_TRIGGER;
1351 	ent->touch = Touch_Item;
1352 
1353 	//can't touch owner for x seconds
1354 	ent->genericValue11 = ent->r.ownerNum;
1355 	ent->genericValue10 = level.time + TOSSED_ITEM_OWNER_NOTOUCH_DUR;
1356 
1357 	//so we know to remove when picked up, not respawn
1358 	ent->genericValue9 = 1;
1359 
1360 	//kind of a lame value to use, but oh well. This means don't
1361 	//pick up this item clientside with prediction, because we
1362 	//aren't sending over all the data necessary for the player
1363 	//to know if he can.
1364 	ent->s.brokenLimbs = 1;
1365 
1366 	//since it uses my server-only physics
1367 	ent->s.eFlags |= EF_CLIENTSMOOTH;
1368 }
1369 
1370 #define DISP_HEALTH_ITEM		"item_medpak_instant"
1371 #define DISP_AMMO_ITEM			"ammo_all"
1372 
G_PrecacheDispensers(void)1373 void G_PrecacheDispensers(void)
1374 {
1375 	gitem_t *item;
1376 
1377 	item = BG_FindItem(DISP_HEALTH_ITEM);
1378 	if (item)
1379 	{
1380 		RegisterItem(item);
1381 	}
1382 
1383 	item = BG_FindItem(DISP_AMMO_ITEM);
1384 	if (item)
1385 	{
1386 		RegisterItem(item);
1387 	}
1388 }
1389 
ItemUse_UseDisp(gentity_t * ent,int type)1390 void ItemUse_UseDisp(gentity_t *ent, int type)
1391 {
1392 	gitem_t *item = NULL;
1393 	gentity_t *eItem;
1394 
1395 	if (!ent->client ||
1396 		ent->client->tossableItemDebounce > level.time)
1397 	{ //can't use it again yet
1398 		return;
1399 	}
1400 
1401 	if (ent->client->ps.weaponTime > 0 ||
1402 		ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
1403 	{ //busy doing something else
1404 		return;
1405 	}
1406 
1407 	ent->client->tossableItemDebounce = level.time + TOSS_DEBOUNCE_TIME;
1408 
1409 	if (type == HI_HEALTHDISP)
1410 	{
1411 		item = BG_FindItem(DISP_HEALTH_ITEM);
1412 	}
1413 	else
1414 	{
1415 		item = BG_FindItem(DISP_AMMO_ITEM);
1416 	}
1417 
1418 	if (item)
1419 	{
1420 		vec3_t fwd, pos;
1421 		gentity_t	*te;
1422 
1423 		eItem = G_Spawn();
1424 		eItem->r.ownerNum = ent->s.number;
1425 		eItem->classname = item->classname;
1426 
1427 		VectorCopy(ent->client->ps.origin, pos);
1428 		pos[2] += ent->client->ps.viewheight;
1429 
1430 		G_SetOrigin(eItem, pos);
1431 		VectorCopy(eItem->r.currentOrigin, eItem->s.origin);
1432 		trap->LinkEntity((sharedEntity_t *)eItem);
1433 
1434 		G_SpecialSpawnItem(eItem, item);
1435 
1436 		AngleVectors(ent->client->ps.viewangles, fwd, NULL, NULL);
1437 		VectorScale(fwd, 128.0f, eItem->epVelocity);
1438 		eItem->epVelocity[2] = 16.0f;
1439 
1440 	//	G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_THERMAL_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
1441 
1442 		te = G_TempEntity( ent->client->ps.origin, EV_LOCALTIMER );
1443 		te->s.time = level.time;
1444 		te->s.time2 = TOSS_DEBOUNCE_TIME;
1445 		te->s.owner = ent->client->ps.clientNum;
1446 	}
1447 }
1448 
1449 
1450 //===============================================
1451 //Portable E-Web -rww
1452 //===============================================
1453 //put the e-web away/remove it from the owner
EWebDisattach(gentity_t * owner,gentity_t * eweb)1454 void EWebDisattach(gentity_t *owner, gentity_t *eweb)
1455 {
1456     owner->client->ewebIndex = 0;
1457 	owner->client->ps.emplacedIndex = 0;
1458 	if (owner->health > 0)
1459 	{
1460 		owner->client->ps.stats[STAT_WEAPONS] = eweb->genericValue11;
1461 	}
1462 	else
1463 	{
1464 		owner->client->ps.stats[STAT_WEAPONS] = 0;
1465 	}
1466 	eweb->think = G_FreeEntity;
1467 	eweb->nextthink = level.time;
1468 }
1469 
1470 //precache misc e-web assets
EWebPrecache(void)1471 void EWebPrecache(void)
1472 {
1473 	RegisterItem( BG_FindItemForWeapon(WP_TURRET) );
1474 	G_EffectIndex("detpack/explosion.efx");
1475 	G_EffectIndex("turret/muzzle_flash.efx");
1476 }
1477 
1478 //e-web death
1479 #define EWEB_DEATH_RADIUS		128
1480 #define EWEB_DEATH_DMG			90
1481 
1482 extern void BG_CycleInven(playerState_t *ps, int direction); //bg_misc.c
1483 
EWebDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)1484 void EWebDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
1485 {
1486 	vec3_t fxDir;
1487 
1488 	G_RadiusDamage(self->r.currentOrigin, self, EWEB_DEATH_DMG, EWEB_DEATH_RADIUS, self, self, MOD_SUICIDE);
1489 
1490 	VectorSet(fxDir, 1.0f, 0.0f, 0.0f);
1491 	G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, fxDir);
1492 
1493 	if (self->r.ownerNum != ENTITYNUM_NONE)
1494 	{
1495 		gentity_t *owner = &g_entities[self->r.ownerNum];
1496 
1497 		if (owner->inuse && owner->client)
1498 		{
1499 			EWebDisattach(owner, self);
1500 
1501 			//make sure it resets next time we spawn one in case we someone obtain one before death
1502 			owner->client->ewebHealth = -1;
1503 
1504 			//take it away from him, it is gone forever.
1505 			owner->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1<<HI_EWEB);
1506 
1507 			if (owner->client->ps.stats[STAT_HOLDABLE_ITEM] > 0 &&
1508 				bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giType == IT_HOLDABLE &&
1509 				bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_EWEB)
1510 			{ //he has it selected so deselect it and select the first thing available
1511 				owner->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
1512 				BG_CycleInven(&owner->client->ps, 1);
1513 			}
1514 		}
1515 	}
1516 }
1517 
1518 //e-web pain
EWebPain(gentity_t * self,gentity_t * attacker,int damage)1519 void EWebPain(gentity_t *self, gentity_t *attacker, int damage)
1520 {
1521 	//update the owner's health status of me
1522 	if (self->r.ownerNum != ENTITYNUM_NONE)
1523 	{
1524 		gentity_t *owner = &g_entities[self->r.ownerNum];
1525 
1526 		if (owner->inuse && owner->client)
1527 		{
1528 			owner->client->ewebHealth = self->health;
1529 		}
1530 	}
1531 }
1532 
1533 //special routine for tracking angles between client and server
EWeb_SetBoneAngles(gentity_t * ent,char * bone,vec3_t angles)1534 void EWeb_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
1535 {
1536 	int *thebone = &ent->s.boneIndex1;
1537 	int *firstFree = NULL;
1538 	int i = 0;
1539 	int boneIndex = G_BoneIndex(bone);
1540 	int flags, up, right, forward;
1541 	vec3_t *boneVector = &ent->s.boneAngles1;
1542 	vec3_t *freeBoneVec = NULL;
1543 
1544 	while (thebone)
1545 	{
1546 		if (!*thebone && !firstFree)
1547 		{ //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
1548 			firstFree = thebone;
1549 			freeBoneVec = boneVector;
1550 		}
1551 		else if (*thebone)
1552 		{
1553 			if (*thebone == boneIndex)
1554 			{ //this is it
1555 				break;
1556 			}
1557 		}
1558 
1559 		switch (i)
1560 		{
1561 		case 0:
1562 			thebone = &ent->s.boneIndex2;
1563 			boneVector = &ent->s.boneAngles2;
1564 			break;
1565 		case 1:
1566 			thebone = &ent->s.boneIndex3;
1567 			boneVector = &ent->s.boneAngles3;
1568 			break;
1569 		case 2:
1570 			thebone = &ent->s.boneIndex4;
1571 			boneVector = &ent->s.boneAngles4;
1572 			break;
1573 		default:
1574 			thebone = NULL;
1575 			boneVector = NULL;
1576 			break;
1577 		}
1578 
1579 		i++;
1580 	}
1581 
1582 	if (!thebone)
1583 	{ //didn't find it, create it
1584 		if (!firstFree)
1585 		{ //no free bones.. can't do a thing then.
1586 			Com_Printf("WARNING: E-Web has no free bone indexes\n");
1587 			return;
1588 		}
1589 
1590 		thebone = firstFree;
1591 
1592 		*thebone = boneIndex;
1593 		boneVector = freeBoneVec;
1594 	}
1595 
1596 	//If we got here then we have a vector and an index.
1597 
1598 	//Copy the angles over the vector in the entitystate, so we can use the corresponding index
1599 	//to set the bone angles on the client.
1600 	VectorCopy(angles, *boneVector);
1601 
1602 	//Now set the angles on our server instance if we have one.
1603 
1604 	if (!ent->ghoul2)
1605 	{
1606 		return;
1607 	}
1608 
1609 	flags = BONE_ANGLES_POSTMULT;
1610 	up = POSITIVE_Y;
1611 	right = NEGATIVE_Z;
1612 	forward = NEGATIVE_X;
1613 
1614 	//first 3 bits is forward, second 3 bits is right, third 3 bits is up
1615 	ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
1616 
1617 	trap->G2API_SetBoneAngles( ent->ghoul2,
1618 					0,
1619 					bone,
1620 					angles,
1621 					flags,
1622 					up,
1623 					right,
1624 					forward,
1625 					NULL,
1626 					100,
1627 					level.time );
1628 }
1629 
1630 //start an animation on model_root both server side and client side
EWeb_SetBoneAnim(gentity_t * eweb,int startFrame,int endFrame)1631 void EWeb_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
1632 {
1633 	//set info on the entity so it knows to start the anim on the client next snapshot.
1634 	eweb->s.eFlags |= EF_G2ANIMATING;
1635 
1636 	if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
1637 	{ //already playing this anim, let's flag it to restart
1638 		eweb->s.torsoFlip = !eweb->s.torsoFlip;
1639 	}
1640 	else
1641 	{
1642 		eweb->s.torsoAnim = startFrame;
1643 		eweb->s.legsAnim = endFrame;
1644 	}
1645 
1646 	//now set the animation on the server ghoul2 instance.
1647 	assert(eweb->ghoul2);
1648 	trap->G2API_SetBoneAnim(eweb->ghoul2, 0, "model_root", startFrame, endFrame,
1649 		(BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
1650 }
1651 
1652 //fire a shot off
1653 #define EWEB_MISSILE_DAMAGE			20
EWebFire(gentity_t * owner,gentity_t * eweb)1654 void EWebFire(gentity_t *owner, gentity_t *eweb)
1655 {
1656 	mdxaBone_t boltMatrix;
1657 	gentity_t *missile;
1658 	vec3_t p, d, bPoint;
1659 
1660 	if (eweb->genericValue10 == -1)
1661 	{ //oh no
1662 		assert(!"Bad e-web bolt");
1663 		return;
1664 	}
1665 
1666 	//get the muzzle point
1667 	trap->G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue10, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
1668 	BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
1669 	BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, d);
1670 
1671 	//Start the thing backwards into the bounding box so it can't start inside other solid things
1672 	VectorMA(p, -16.0f, d, bPoint);
1673 
1674 	//create the missile
1675 	missile = CreateMissile( bPoint, d, 1200.0f, 10000, owner, qfalse );
1676 
1677 	missile->classname = "generic_proj";
1678 	missile->s.weapon = WP_TURRET;
1679 
1680 	missile->damage = EWEB_MISSILE_DAMAGE;
1681 	missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1682 	missile->methodOfDeath = MOD_TURBLAST;
1683 	missile->clipmask = (MASK_SHOT|CONTENTS_LIGHTSABER);
1684 
1685 	//ignore the e-web entity
1686 	missile->passThroughNum = eweb->s.number+1;
1687 
1688 	//times it can bounce before it dies
1689 	missile->bounceCount = 8;
1690 
1691 	//play the muzzle flash
1692 	vectoangles(d, d);
1693 	G_PlayEffectID(G_EffectIndex("turret/muzzle_flash.efx"), p, d);
1694 }
1695 
1696 //lock the owner into place relative to the cannon pos
EWebPositionUser(gentity_t * owner,gentity_t * eweb)1697 void EWebPositionUser(gentity_t *owner, gentity_t *eweb)
1698 {
1699 	mdxaBone_t boltMatrix;
1700 	vec3_t p, d;
1701 	trace_t tr;
1702 
1703 	trap->G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue9, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
1704 	BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
1705 	BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d);
1706 
1707 	VectorMA(p, 32.0f, d, p);
1708 	p[2] = eweb->r.currentOrigin[2];
1709 
1710 	p[2] += 4.0f;
1711 
1712 	trap->Trace(&tr, owner->client->ps.origin, owner->r.mins, owner->r.maxs, p, owner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1713 
1714 	if (!tr.startsolid && !tr.allsolid && tr.fraction == 1.0f)
1715 	{ //all clear, we can move there
1716 		vec3_t pDown;
1717 
1718 		VectorCopy(p, pDown);
1719 		pDown[2] -= 7.0f;
1720 		trap->Trace(&tr, p, owner->r.mins, owner->r.maxs, pDown, owner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1721 
1722 		if (!tr.startsolid && !tr.allsolid)
1723 		{
1724 			VectorSubtract(owner->client->ps.origin, tr.endpos, d);
1725 			if (VectorLength(d) > 1.0f)
1726 			{ //we moved, do some animating
1727 				vec3_t dAng;
1728 				int aFlags = SETANIM_FLAG_HOLD;
1729 
1730 				vectoangles(d, dAng);
1731 				dAng[YAW] = AngleSubtract(owner->client->ps.viewangles[YAW], dAng[YAW]);
1732 				if (dAng[YAW] > 0.0f)
1733 				{
1734 					if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1)
1735 					{ //reset to change direction
1736 						aFlags |= SETANIM_FLAG_OVERRIDE;
1737 					}
1738 					G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_LEFT1, aFlags, 0);
1739 				}
1740 				else
1741 				{
1742 					if (owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
1743 					{ //reset to change direction
1744 						aFlags |= SETANIM_FLAG_OVERRIDE;
1745 					}
1746 					G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_RIGHT1, aFlags, 0);
1747 				}
1748 			}
1749 			else if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1 || owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
1750 			{ //don't keep animating in place
1751 				owner->client->ps.legsTimer = 0;
1752 			}
1753 
1754 			G_SetOrigin(owner, tr.endpos);
1755 			VectorCopy(tr.endpos, owner->client->ps.origin);
1756 		}
1757 	}
1758 	else
1759 	{ //can't move here.. stop using the thing I guess
1760 		EWebDisattach(owner, eweb);
1761 	}
1762 }
1763 
1764 //keep the bone angles updated based on the owner's look dir
EWebUpdateBoneAngles(gentity_t * owner,gentity_t * eweb)1765 void EWebUpdateBoneAngles(gentity_t *owner, gentity_t *eweb)
1766 {
1767 	vec3_t yAng;
1768 	float ideal;
1769 	float incr;
1770 	const float turnCap = 4.0f; //max degrees we can turn per update
1771 
1772 	VectorClear(yAng);
1773 	ideal = AngleSubtract(owner->client->ps.viewangles[YAW], eweb->s.angles[YAW]);
1774 	incr = AngleSubtract(ideal, eweb->angle);
1775 
1776 	if (incr > turnCap)
1777 	{
1778 		incr = turnCap;
1779 	}
1780 	else if (incr < -turnCap)
1781 	{
1782 		incr = -turnCap;
1783 	}
1784 
1785 	eweb->angle += incr;
1786 
1787 	yAng[0] = eweb->angle;
1788 	EWeb_SetBoneAngles(eweb, "cannon_Yrot", yAng);
1789 
1790 	EWebPositionUser(owner, eweb);
1791 	if (!owner->client->ewebIndex)
1792 	{ //was removed during position function
1793 		return;
1794 	}
1795 
1796 	VectorClear(yAng);
1797 	yAng[2] = AngleSubtract(owner->client->ps.viewangles[PITCH], eweb->s.angles[PITCH])*0.8f;
1798 	EWeb_SetBoneAngles(eweb, "cannon_Xrot", yAng);
1799 }
1800 
1801 //keep it updated
1802 extern int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); //bg_misc.c
1803 
EWebThink(gentity_t * self)1804 void EWebThink(gentity_t *self)
1805 {
1806 	qboolean killMe = qfalse;
1807 	const float gravity = 3.0f;
1808 	const float mass = 0.09f;
1809 	const float bounce = 1.1f;
1810 
1811 	if (self->r.ownerNum == ENTITYNUM_NONE)
1812 	{
1813 		killMe = qtrue;
1814 	}
1815 	else
1816 	{
1817 		gentity_t *owner = &g_entities[self->r.ownerNum];
1818 
1819 		if (!owner->inuse || !owner->client || owner->client->pers.connected != CON_CONNECTED ||
1820 			owner->client->ewebIndex != self->s.number || owner->health < 1)
1821 		{
1822 			killMe = qtrue;
1823 		}
1824 		else if (owner->client->ps.emplacedIndex != self->s.number)
1825 		{ //just go back to the inventory then
1826 			EWebDisattach(owner, self);
1827 			return;
1828 		}
1829 
1830 		if (!killMe)
1831 		{
1832 			float yaw;
1833 
1834 			if (BG_EmplacedView(owner->client->ps.viewangles, self->s.angles, &yaw, self->s.origin2[0]))
1835 			{
1836 				owner->client->ps.viewangles[YAW] = yaw;
1837 			}
1838 			owner->client->ps.weapon = WP_EMPLACED_GUN;
1839 			owner->client->ps.stats[STAT_WEAPONS] = WP_EMPLACED_GUN;
1840 
1841 			if (self->genericValue8 < level.time)
1842 			{ //make sure the anim timer is done
1843 				EWebUpdateBoneAngles(owner, self);
1844 				if (!owner->client->ewebIndex)
1845 				{ //was removed during position function
1846 					return;
1847 				}
1848 
1849 				if (owner->client->pers.cmd.buttons & BUTTON_ATTACK)
1850 				{
1851 					if (self->genericValue5 < level.time)
1852 					{ //we can fire another shot off
1853 						EWebFire(owner, self);
1854 
1855 						//cheap firing anim
1856 						EWeb_SetBoneAnim(self, 2, 4);
1857 						self->genericValue3 = 1;
1858 
1859 						//set fire debounce time
1860 						self->genericValue5 = level.time + 100;
1861 					}
1862 				}
1863 				else if (self->genericValue5 < level.time && self->genericValue3)
1864 				{ //reset the anim back to non-firing
1865 					EWeb_SetBoneAnim(self, 0, 1);
1866 					self->genericValue3 = 0;
1867 				}
1868 			}
1869 		}
1870 	}
1871 
1872 	if (killMe)
1873 	{ //something happened to the owner, let's explode
1874 		EWebDie(self, self, self, 999, MOD_SUICIDE);
1875 		return;
1876 	}
1877 
1878 	//run some physics on it real quick so it falls and stuff properly
1879 	G_RunExPhys(self, gravity, mass, bounce, qfalse, NULL, 0);
1880 
1881 	self->nextthink = level.time;
1882 }
1883 
1884 #define EWEB_HEALTH			200
1885 
1886 //spawn and set up an e-web ent
EWeb_Create(gentity_t * spawner)1887 gentity_t *EWeb_Create(gentity_t *spawner)
1888 {
1889 	const char *modelName = "models/map_objects/hoth/eweb_model.glm";
1890 	int failSound = G_SoundIndex("sound/interface/shieldcon_empty");
1891 	gentity_t *ent;
1892 	trace_t tr;
1893 	vec3_t fAng, fwd, pos, downPos, s;
1894 	vec3_t mins, maxs;
1895 
1896 	VectorSet(mins, -32, -32, -24);
1897 	VectorSet(maxs, 32, 32, 24);
1898 
1899 	VectorSet(fAng, 0, spawner->client->ps.viewangles[1], 0);
1900 	AngleVectors(fAng, fwd, 0, 0);
1901 
1902 	VectorCopy(spawner->client->ps.origin, s);
1903 	//allow some fudge
1904 	s[2] += 12.0f;
1905 
1906 	VectorMA(s, 48.0f, fwd, pos);
1907 
1908 	trap->Trace(&tr, s, mins, maxs, pos, spawner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1909 
1910 	if (tr.allsolid || tr.startsolid || tr.fraction != 1.0f)
1911 	{ //can't spawn here, we are in solid
1912 		G_Sound(spawner, CHAN_AUTO, failSound);
1913 		return NULL;
1914 	}
1915 
1916 	ent = G_Spawn();
1917 
1918 	ent->clipmask = MASK_PLAYERSOLID;
1919 	ent->r.contents = MASK_PLAYERSOLID;
1920 
1921 	ent->physicsObject = qtrue;
1922 
1923 	//for the sake of being able to differentiate client-side between this and an emplaced gun
1924 	ent->s.weapon = WP_NONE;
1925 
1926 	VectorCopy(pos, downPos);
1927 	downPos[2] -= 18.0f;
1928 	trap->Trace(&tr, pos, mins, maxs, downPos, spawner->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
1929 
1930 	if (tr.startsolid || tr.allsolid || tr.fraction == 1.0f || tr.entityNum < ENTITYNUM_WORLD)
1931 	{ //didn't hit ground.
1932 		G_FreeEntity(ent);
1933 		G_Sound(spawner, CHAN_AUTO, failSound);
1934 		return NULL;
1935 	}
1936 
1937 	VectorCopy(tr.endpos, pos);
1938 
1939 	G_SetOrigin(ent, pos);
1940 
1941 	VectorCopy(fAng, ent->s.apos.trBase);
1942 	VectorCopy(fAng, ent->r.currentAngles);
1943 
1944 	ent->s.owner = spawner->s.number;
1945 	ent->s.teamowner = spawner->client->sess.sessionTeam;
1946 
1947 	ent->takedamage = qtrue;
1948 
1949 	if (spawner->client->ewebHealth <= 0)
1950 	{ //refresh the owner's e-web health if its last e-web did not exist or was killed
1951 		spawner->client->ewebHealth = EWEB_HEALTH;
1952 	}
1953 
1954 	//resume health of last deployment
1955 	ent->maxHealth = EWEB_HEALTH;
1956 	ent->health = spawner->client->ewebHealth;
1957 	G_ScaleNetHealth(ent);
1958 
1959 	ent->die = EWebDie;
1960 	ent->pain = EWebPain;
1961 
1962 	ent->think = EWebThink;
1963 	ent->nextthink = level.time;
1964 
1965 	//set up the g2 model info
1966 	ent->s.modelGhoul2 = 1;
1967 	ent->s.g2radius = 128;
1968 	ent->s.modelindex = G_ModelIndex((char *)modelName);
1969 
1970 	trap->G2API_InitGhoul2Model(&ent->ghoul2, modelName, 0, 0, 0, 0, 0);
1971 
1972 	if (!ent->ghoul2)
1973 	{ //should not happen, but just to be safe.
1974 		G_FreeEntity(ent);
1975 		return NULL;
1976 	}
1977 
1978 	//initialize bone angles
1979 	EWeb_SetBoneAngles(ent, "cannon_Yrot", vec3_origin);
1980 	EWeb_SetBoneAngles(ent, "cannon_Xrot", vec3_origin);
1981 
1982 	ent->genericValue10 = trap->G2API_AddBolt(ent->ghoul2, 0, "*cannonflash"); //muzzle bolt
1983 	ent->genericValue9 = trap->G2API_AddBolt(ent->ghoul2, 0, "cannon_Yrot"); //for placing the owner relative to rotation
1984 
1985 	//set the constraints for this guy as an emplaced weapon, and his constraint angles
1986 	ent->s.origin2[0] = 360.0f; //360 degrees in either direction
1987 
1988 	VectorCopy(fAng, ent->s.angles); //consider "angle 0" for constraint
1989 
1990 	//angle of y rot bone
1991 	ent->angle = 0.0f;
1992 
1993 	ent->r.ownerNum = spawner->s.number;
1994 	trap->LinkEntity((sharedEntity_t *)ent);
1995 
1996 	//store off the owner's current weapons, we will be forcing him to use the "emplaced" weapon
1997 	ent->genericValue11 = spawner->client->ps.stats[STAT_WEAPONS];
1998 
1999 	//start the "unfolding" anim
2000 	EWeb_SetBoneAnim(ent, 4, 20);
2001 	//don't allow use until the anim is done playing (rough time estimate)
2002 	ent->genericValue8 = level.time + 500;
2003 
2004 	VectorCopy(mins, ent->r.mins);
2005 	VectorCopy(maxs, ent->r.maxs);
2006 
2007 	return ent;
2008 }
2009 
2010 #define EWEB_USE_DEBOUNCE		1000
2011 //use the e-web
ItemUse_UseEWeb(gentity_t * ent)2012 void ItemUse_UseEWeb(gentity_t *ent)
2013 {
2014 	if (ent->client->ewebTime > level.time)
2015 	{ //can't use again yet
2016 		return;
2017 	}
2018 
2019 	if (ent->client->ps.weaponTime > 0 ||
2020 		ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
2021 	{ //busy doing something else
2022 		return;
2023 	}
2024 
2025 	if (ent->client->ps.emplacedIndex && !ent->client->ewebIndex)
2026 	{ //using an emplaced gun already that isn't our own e-web
2027 		return;
2028 	}
2029 
2030 	if (ent->client->ewebIndex)
2031 	{ //put it away
2032 		EWebDisattach(ent, &g_entities[ent->client->ewebIndex]);
2033 	}
2034 	else
2035 	{ //create it
2036 		gentity_t *eweb = EWeb_Create(ent);
2037 
2038 		if (eweb)
2039 		{ //if it's null the thing couldn't spawn (probably no room)
2040 			ent->client->ewebIndex = eweb->s.number;
2041 			ent->client->ps.emplacedIndex = eweb->s.number;
2042 		}
2043 	}
2044 
2045 	ent->client->ewebTime = level.time + EWEB_USE_DEBOUNCE;
2046 }
2047 //===============================================
2048 //End E-Web
2049 //===============================================
2050 
2051 
Pickup_Powerup(gentity_t * ent,gentity_t * other)2052 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
2053 	int			quantity;
2054 	int			i;
2055 	gclient_t	*client;
2056 
2057 	if ( !other->client->ps.powerups[ent->item->giTag] ) {
2058 		// round timing to seconds to make multiple powerup timers
2059 		// count in sync
2060 		other->client->ps.powerups[ent->item->giTag] =
2061 			level.time - ( level.time % 1000 );
2062 
2063 		G_LogWeaponPowerup(other->s.number, ent->item->giTag);
2064 	}
2065 
2066 	if ( ent->count ) {
2067 		quantity = ent->count;
2068 	} else {
2069 		quantity = ent->item->quantity;
2070 	}
2071 
2072 	other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
2073 
2074 	if (ent->item->giTag == PW_YSALAMIRI)
2075 	{
2076 		other->client->ps.powerups[PW_FORCE_ENLIGHTENED_LIGHT] = 0;
2077 		other->client->ps.powerups[PW_FORCE_ENLIGHTENED_DARK] = 0;
2078 		other->client->ps.powerups[PW_FORCE_BOON] = 0;
2079 	}
2080 
2081 	// give any nearby players a "denied" anti-reward
2082 	for ( i = 0 ; i < level.maxclients ; i++ ) {
2083 		vec3_t		delta;
2084 		float		len;
2085 		vec3_t		forward;
2086 		trace_t		tr;
2087 
2088 		client = &level.clients[i];
2089 		if ( client == other->client ) {
2090 			continue;
2091 		}
2092 		if ( client->pers.connected == CON_DISCONNECTED ) {
2093 			continue;
2094 		}
2095 		if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
2096 			continue;
2097 		}
2098 
2099     // if same team in team game, no sound
2100     // cannot use OnSameTeam as it expects to g_entities, not clients
2101   	if ( level.gametype >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam  ) {
2102       continue;
2103     }
2104 
2105 		// if too far away, no sound
2106 		VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
2107 		len = VectorNormalize( delta );
2108 		if ( len > 192 ) {
2109 			continue;
2110 		}
2111 
2112 		// if not facing, no sound
2113 		AngleVectors( client->ps.viewangles, forward, NULL, NULL );
2114 		if ( DotProduct( delta, forward ) < 0.4 ) {
2115 			continue;
2116 		}
2117 
2118 		// if not line of sight, no sound
2119 		trap->Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID, qfalse, 0, 0 );
2120 		if ( tr.fraction != 1.0 ) {
2121 			continue;
2122 		}
2123 
2124 		// anti-reward
2125 		client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
2126 	}
2127 	return RESPAWN_POWERUP;
2128 }
2129 
2130 //======================================================================
2131 
Pickup_Holdable(gentity_t * ent,gentity_t * other)2132 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
2133 
2134 	other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
2135 
2136 	other->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << ent->item->giTag);
2137 
2138 	G_LogWeaponItem(other->s.number, ent->item->giTag);
2139 
2140 	return adjustRespawnTime(RESPAWN_HOLDABLE, ent->item->giType, ent->item->giTag);
2141 }
2142 
2143 
2144 //======================================================================
2145 
Add_Ammo(gentity_t * ent,int weapon,int count)2146 void Add_Ammo (gentity_t *ent, int weapon, int count)
2147 {
2148 	int max = ammoData[weapon].max;
2149 
2150 	if (ent->client->ps.eFlags & EF_DOUBLE_AMMO)
2151 	{ // fix: double ammo for siege
2152 		max *= 2;
2153 	}
2154 
2155 	if ( ent->client->ps.ammo[weapon] < max )
2156 	{
2157 		ent->client->ps.ammo[weapon] += count;
2158 		if ( ent->client->ps.ammo[weapon] > max )
2159 		{
2160 			ent->client->ps.ammo[weapon] = max;
2161 		}
2162 	}
2163 }
2164 
Pickup_Ammo(gentity_t * ent,gentity_t * other)2165 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
2166 {
2167 	int		quantity;
2168 
2169 	if ( ent->count ) {
2170 		quantity = ent->count;
2171 	} else {
2172 		quantity = ent->item->quantity;
2173 	}
2174 
2175 	if (ent->item->giTag == -1)
2176 	{ //an ammo_all, give them a bit of everything
2177 		if ( level.gametype == GT_SIEGE )	// complaints that siege tech's not giving enough ammo.  Does anything else use ammo all?
2178 		{
2179 			Add_Ammo(other, AMMO_BLASTER, 100);
2180 			Add_Ammo(other, AMMO_POWERCELL, 100);
2181 			Add_Ammo(other, AMMO_METAL_BOLTS, 100);
2182 			Add_Ammo(other, AMMO_ROCKETS, 5);
2183 			if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_DET_PACK))
2184 			{
2185 				Add_Ammo(other, AMMO_DETPACK, 2);
2186 			}
2187 			if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_THERMAL))
2188 			{
2189 				Add_Ammo(other, AMMO_THERMAL, 2);
2190 			}
2191 			if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_TRIP_MINE))
2192 			{
2193 				Add_Ammo(other, AMMO_TRIPMINE, 2);
2194 			}
2195 		}
2196 		else
2197 		{
2198 			Add_Ammo(other, AMMO_BLASTER, 50);
2199 			Add_Ammo(other, AMMO_POWERCELL, 50);
2200 			Add_Ammo(other, AMMO_METAL_BOLTS, 50);
2201 			Add_Ammo(other, AMMO_ROCKETS, 2);
2202 		}
2203 	}
2204 	else
2205 	{
2206 		Add_Ammo (other, ent->item->giTag, quantity);
2207 	}
2208 
2209 	return adjustRespawnTime(RESPAWN_AMMO, ent->item->giType, ent->item->giTag);
2210 }
2211 
2212 //======================================================================
2213 
2214 
Pickup_Weapon(gentity_t * ent,gentity_t * other)2215 int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
2216 	int		quantity;
2217 
2218 	if ( ent->count < 0 ) {
2219 		quantity = 0; // None for you, sir!
2220 	} else {
2221 		if ( ent->count ) {
2222 			quantity = ent->count;
2223 		} else {
2224 			quantity = ent->item->quantity;
2225 		}
2226 
2227 		// dropped items and teamplay weapons always have full ammo
2228 		if ( ! (ent->flags & FL_DROPPED_ITEM) && level.gametype != GT_TEAM ) {
2229 			// respawning rules
2230 
2231 			// New method:  If the player has less than half the minimum, give them the minimum, else add 1/2 the min.
2232 
2233 			// drop the quantity if the already have over the minimum
2234 			if ( other->client->ps.ammo[ ent->item->giTag ] < quantity*0.5 ) {
2235 				quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
2236 			} else {
2237 				quantity = quantity*0.5;		// only add half the value.
2238 			}
2239 
2240 			// Old method:  If the player has less than the minimum, give them the minimum, else just add 1.
2241 /*
2242 			// drop the quantity if the already have over the minimum
2243 			if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
2244 				quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
2245 			} else {
2246 				quantity = 1;		// only add a single shot
2247 			}
2248 			*/
2249 		}
2250 	}
2251 
2252 	// add the weapon
2253 	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
2254 
2255 	//Add_Ammo( other, ent->item->giTag, quantity );
2256 	Add_Ammo( other, weaponData[ent->item->giTag].ammoIndex, quantity );
2257 
2258 	G_LogWeaponPickup(other->s.number, ent->item->giTag);
2259 
2260 	// team deathmatch has slow weapon respawns
2261 	if ( level.gametype == GT_TEAM )
2262 	{
2263 		return adjustRespawnTime(RESPAWN_TEAM_WEAPON, ent->item->giType, ent->item->giTag);
2264 	}
2265 
2266 	return adjustRespawnTime(g_weaponRespawn.integer, ent->item->giType, ent->item->giTag);
2267 }
2268 
2269 
2270 //======================================================================
2271 
Pickup_Health(gentity_t * ent,gentity_t * other)2272 int Pickup_Health (gentity_t *ent, gentity_t *other) {
2273 	int			max;
2274 	int			quantity;
2275 
2276 	// small and mega healths will go over the max
2277 	if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
2278 		max = other->client->ps.stats[STAT_MAX_HEALTH];
2279 	} else {
2280 		max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
2281 	}
2282 
2283 	if ( ent->count ) {
2284 		quantity = ent->count;
2285 	} else {
2286 		quantity = ent->item->quantity;
2287 	}
2288 
2289 	other->health += quantity;
2290 
2291 	if (other->health > max ) {
2292 		other->health = max;
2293 	}
2294 	other->client->ps.stats[STAT_HEALTH] = other->health;
2295 
2296 	if ( ent->item->quantity == 100 ) {		// mega health respawns slow
2297 		return RESPAWN_MEGAHEALTH;
2298 	}
2299 
2300 	return adjustRespawnTime(RESPAWN_HEALTH, ent->item->giType, ent->item->giTag);
2301 }
2302 
2303 //======================================================================
2304 
Pickup_Armor(gentity_t * ent,gentity_t * other)2305 int Pickup_Armor( gentity_t *ent, gentity_t *other )
2306 {
2307 	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
2308 	if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag )
2309 	{
2310 		other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag;
2311 	}
2312 
2313 	return adjustRespawnTime(RESPAWN_ARMOR, ent->item->giType, ent->item->giTag);
2314 }
2315 
2316 //======================================================================
2317 
2318 /*
2319 ===============
2320 RespawnItem
2321 ===============
2322 */
RespawnItem(gentity_t * ent)2323 void RespawnItem( gentity_t *ent ) {
2324 	// randomly select from teamed entities
2325 	if (ent->team) {
2326 		gentity_t	*master;
2327 		int	count;
2328 		int choice;
2329 
2330 		if ( !ent->teammaster ) {
2331 			trap->Error( ERR_DROP, "RespawnItem: bad teammaster");
2332 		}
2333 		master = ent->teammaster;
2334 
2335 		for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
2336 			;
2337 
2338 		choice = rand() % count;
2339 
2340 		for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
2341 			;
2342 	}
2343 
2344 	ent->r.contents = CONTENTS_TRIGGER;
2345 	//ent->s.eFlags &= ~EF_NODRAW;
2346 	ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER);
2347 	ent->r.svFlags &= ~SVF_NOCLIENT;
2348 	trap->LinkEntity ((sharedEntity_t *)ent);
2349 
2350 	if ( ent->item->giType == IT_POWERUP ) {
2351 		// play powerup spawn sound to all clients
2352 		gentity_t	*te;
2353 
2354 		// if the powerup respawn sound should Not be global
2355 		if (ent->speed) {
2356 			te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
2357 		}
2358 		else {
2359 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
2360 		}
2361 		te->s.eventParm = G_SoundIndex( "sound/items/respawn1" );
2362 		te->r.svFlags |= SVF_BROADCAST;
2363 	}
2364 
2365 	// play the normal respawn sound only to nearby clients
2366 	G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
2367 
2368 	ent->nextthink = 0;
2369 }
2370 
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)2371 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
2372 {
2373 	if ( (item->flags&FL_DROPPED_ITEM)
2374 		&& item->activator != &g_entities[0]
2375 		&& pickerupper->s.number
2376 		&& pickerupper->s.weapon == WP_NONE
2377 		&& pickerupper->enemy
2378 		&& pickerupper->painDebounceTime < level.time
2379 		&& pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
2380 		&& !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) //not being forced to march
2381 		/*&& item->item->giTag != INV_SECURITY_KEY*/ )
2382 	{//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
2383 		if ( level.time - item->s.time < 3000 )//was 5000
2384 		{
2385 			return qfalse;
2386 		}
2387 		return qtrue;
2388 	}
2389 	return qfalse;
2390 }
2391 
2392 /*
2393 ===============
2394 Touch_Item
2395 ===============
2396 */
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)2397 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
2398 	int			respawn;
2399 	qboolean	predict;
2400 
2401 	if (ent->genericValue10 > level.time &&
2402 		other &&
2403 		other->s.number == ent->genericValue11)
2404 	{ //this is the ent that we don't want to be able to touch us for x seconds
2405 		return;
2406 	}
2407 
2408 	if (ent->s.eFlags & EF_ITEMPLACEHOLDER)
2409 	{
2410 		return;
2411 	}
2412 
2413 	if (ent->s.eFlags & EF_NODRAW)
2414 	{
2415 		return;
2416 	}
2417 
2418 	if (ent->item->giType == IT_WEAPON &&
2419 		ent->s.powerups &&
2420 		ent->s.powerups < level.time)
2421 	{
2422 		ent->s.generic1 = 0;
2423 		ent->s.powerups = 0;
2424 	}
2425 
2426 	if (!other->client)
2427 		return;
2428 	if (other->health < 1)
2429 		return;		// dead people can't pickup
2430 
2431 	if (ent->item->giType == IT_POWERUP &&
2432 		(ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK))
2433 	{
2434 		if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT)
2435 		{
2436 			if (other->client->ps.fd.forceSide != FORCE_LIGHTSIDE)
2437 			{
2438 				return;
2439 			}
2440 		}
2441 		else
2442 		{
2443 			if (other->client->ps.fd.forceSide != FORCE_DARKSIDE)
2444 			{
2445 				return;
2446 			}
2447 		}
2448 	}
2449 
2450 	// the same pickup rules are used for client side and server side
2451 	if ( !BG_CanItemBeGrabbed( level.gametype, &ent->s, &other->client->ps ) ) {
2452 		return;
2453 	}
2454 
2455 
2456 	if ( other->client->NPC_class == CLASS_ATST ||
2457 		other->client->NPC_class == CLASS_GONK ||
2458 		other->client->NPC_class == CLASS_MARK1 ||
2459 		other->client->NPC_class == CLASS_MARK2 ||
2460 		other->client->NPC_class == CLASS_MOUSE ||
2461 		other->client->NPC_class == CLASS_PROBE ||
2462 		other->client->NPC_class == CLASS_PROTOCOL ||
2463 		other->client->NPC_class == CLASS_R2D2 ||
2464 		other->client->NPC_class == CLASS_R5D2 ||
2465 		other->client->NPC_class == CLASS_SEEKER ||
2466 		other->client->NPC_class == CLASS_REMOTE ||
2467 		other->client->NPC_class == CLASS_RANCOR ||
2468 		other->client->NPC_class == CLASS_WAMPA ||
2469 		//other->client->NPC_class == CLASS_JAWA || //FIXME: in some cases it's okay?
2470 		other->client->NPC_class == CLASS_UGNAUGHT || //FIXME: in some cases it's okay?
2471 		other->client->NPC_class == CLASS_SENTRY )
2472 	{//FIXME: some flag would be better
2473 		//droids can't pick up items/weapons!
2474 		return;
2475 	}
2476 
2477 	if ( CheckItemCanBePickedUpByNPC( ent, other ) )
2478 	{
2479 		if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent )
2480 		{//they were running to pick me up, they did, so clear goal
2481 			other->NPC->goalEntity = NULL;
2482 			other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
2483 		}
2484 	}
2485 	else if ( !(ent->spawnflags &  ITMSF_ALLOWNPC) )
2486 	{// NPCs cannot pick it up
2487 		if ( other->s.eType == ET_NPC )
2488 		{// Not the player?
2489 			qboolean dontGo = qfalse;
2490 			if (ent->item->giType == IT_AMMO &&
2491 				ent->item->giTag == -1 &&
2492 				other->s.NPC_class == CLASS_VEHICLE &&
2493 				other->m_pVehicle &&
2494 				other->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
2495 			{ //yeah, uh, atst gets healed by these things
2496                 if (other->maxHealth &&
2497 					other->health < other->maxHealth)
2498 				{
2499 					other->health += 80;
2500 					if (other->health > other->maxHealth)
2501 					{
2502 						other->health = other->maxHealth;
2503 					}
2504 					G_ScaleNetHealth(other);
2505 					dontGo = qtrue;
2506 				}
2507 			}
2508 
2509 			if (!dontGo)
2510 			{
2511 				return;
2512 			}
2513 		}
2514 	}
2515 
2516 	G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
2517 
2518 	predict = other->client->pers.predictItemPickup;
2519 
2520 	// call the item-specific pickup function
2521 	switch( ent->item->giType ) {
2522 	case IT_WEAPON:
2523 		respawn = Pickup_Weapon(ent, other);
2524 //		predict = qfalse;
2525 		predict = qtrue;
2526 		break;
2527 	case IT_AMMO:
2528 		respawn = Pickup_Ammo(ent, other);
2529 		if (ent->item->giTag == AMMO_THERMAL || ent->item->giTag == AMMO_TRIPMINE || ent->item->giTag == AMMO_DETPACK)
2530 		{
2531 			int weapForAmmo = 0;
2532 
2533 			if (ent->item->giTag == AMMO_THERMAL)
2534 			{
2535 				weapForAmmo = WP_THERMAL;
2536 			}
2537 			else if (ent->item->giTag == AMMO_TRIPMINE)
2538 			{
2539 				weapForAmmo = WP_TRIP_MINE;
2540 			}
2541 			else
2542 			{
2543 				weapForAmmo = WP_DET_PACK;
2544 			}
2545 
2546 			if (other && other->client && other->client->ps.ammo[weaponData[weapForAmmo].ammoIndex] > 0 )
2547 			{
2548 				other->client->ps.stats[STAT_WEAPONS] |= (1 << weapForAmmo);
2549 			}
2550 		}
2551 //		predict = qfalse;
2552 		predict = qtrue;
2553 		break;
2554 	case IT_ARMOR:
2555 		respawn = Pickup_Armor(ent, other);
2556 //		predict = qfalse;
2557 		predict = qtrue;
2558 		break;
2559 	case IT_HEALTH:
2560 		respawn = Pickup_Health(ent, other);
2561 //		predict = qfalse;
2562 		predict = qtrue;
2563 		break;
2564 	case IT_POWERUP:
2565 		respawn = Pickup_Powerup(ent, other);
2566 		predict = qfalse;
2567 //		predict = qtrue;
2568 		break;
2569 	case IT_TEAM:
2570 		respawn = Pickup_Team(ent, other);
2571 		break;
2572 	case IT_HOLDABLE:
2573 		respawn = Pickup_Holdable(ent, other);
2574 		break;
2575 	default:
2576 		return;
2577 	}
2578 
2579 	if ( !respawn ) {
2580 		return;
2581 	}
2582 
2583 	// play the normal pickup sound
2584 	if (predict) {
2585 		if (other->client)
2586 		{
2587 			BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, ent->s.number, &other->client->ps);
2588 		}
2589 		else
2590 		{
2591 			G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.number );
2592 		}
2593 	} else {
2594 		G_AddEvent( other, EV_ITEM_PICKUP, ent->s.number );
2595 	}
2596 
2597 	// powerup pickups are global broadcasts
2598 	if ( /*ent->item->giType == IT_POWERUP ||*/ ent->item->giType == IT_TEAM) {
2599 		// if we want the global sound to play
2600 		if (!ent->speed) {
2601 			gentity_t	*te;
2602 
2603 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
2604 			te->s.eventParm = ent->s.modelindex;
2605 			te->r.svFlags |= SVF_BROADCAST;
2606 		} else {
2607 			gentity_t	*te;
2608 
2609 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
2610 			te->s.eventParm = ent->s.modelindex;
2611 			// only send this temp entity to a single client
2612 			te->r.svFlags |= SVF_SINGLECLIENT;
2613 			te->r.singleClient = other->s.number;
2614 		}
2615 	}
2616 
2617 	// fire item targets
2618 	G_UseTargets (ent, other);
2619 
2620 	// wait of -1 will not respawn
2621 	if ( ent->wait == -1 ) {
2622 		ent->r.svFlags |= SVF_NOCLIENT;
2623 		ent->s.eFlags |= EF_NODRAW;
2624 		ent->r.contents = 0;
2625 		ent->unlinkAfterEvent = qtrue;
2626 		return;
2627 	}
2628 
2629 	// non zero wait overrides respawn time
2630 	if ( ent->wait ) {
2631 		respawn = ent->wait;
2632 	}
2633 
2634 	// random can be used to vary the respawn time
2635 	if ( ent->random ) {
2636 		respawn += Q_flrand(-1.0f, 1.0f) * ent->random;
2637 		if ( respawn < 1 ) {
2638 			respawn = 1;
2639 		}
2640 	}
2641 
2642 	// dropped items will not respawn
2643 	if ( ent->flags & FL_DROPPED_ITEM ) {
2644 		ent->freeAfterEvent = qtrue;
2645 	}
2646 
2647 	// picked up items still stay around, they just don't
2648 	// draw anything.  This allows respawnable items
2649 	// to be placed on movers.
2650 	if (!(ent->flags & FL_DROPPED_ITEM) && (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP))
2651 	{
2652 		ent->s.eFlags |= EF_ITEMPLACEHOLDER;
2653 		ent->s.eFlags &= ~EF_NODRAW;
2654 	}
2655 	else
2656 	{
2657 		ent->s.eFlags |= EF_NODRAW;
2658 		ent->r.svFlags |= SVF_NOCLIENT;
2659 	}
2660 	ent->r.contents = 0;
2661 
2662 	if (ent->genericValue9)
2663 	{ //dropped item, should be removed when picked up
2664 		ent->think = G_FreeEntity;
2665 		ent->nextthink = level.time;
2666 		return;
2667 	}
2668 
2669 	// ZOID
2670 	// A negative respawn times means to never respawn this item (but don't
2671 	// delete it).  This is used by items that are respawned by third party
2672 	// events such as ctf flags
2673 	if ( respawn <= 0 ) {
2674 		ent->nextthink = 0;
2675 		ent->think = 0;
2676 	} else {
2677 		ent->nextthink = level.time + respawn * 1000;
2678 		ent->think = RespawnItem;
2679 	}
2680 	trap->LinkEntity( (sharedEntity_t *)ent );
2681 }
2682 
2683 
2684 //======================================================================
2685 
2686 /*
2687 ================
2688 LaunchItem
2689 
2690 Spawns an item and tosses it forward
2691 ================
2692 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity)2693 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
2694 	gentity_t	*dropped;
2695 
2696 	dropped = G_Spawn();
2697 
2698 	dropped->s.eType = ET_ITEM;
2699 	dropped->s.modelindex = item - bg_itemlist;	// store item number in modelindex
2700 	if (dropped->s.modelindex < 0)
2701 	{
2702 		dropped->s.modelindex = 0;
2703 	}
2704 	dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
2705 
2706 	dropped->classname = item->classname;
2707 	dropped->item = item;
2708 	VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
2709 	VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
2710 
2711 	dropped->r.contents = CONTENTS_TRIGGER;
2712 
2713 	dropped->touch = Touch_Item;
2714 
2715 	G_SetOrigin( dropped, origin );
2716 	dropped->s.pos.trType = TR_GRAVITY;
2717 	dropped->s.pos.trTime = level.time;
2718 	VectorCopy( velocity, dropped->s.pos.trDelta );
2719 
2720 	dropped->flags |= FL_BOUNCE_HALF;
2721 	if ((level.gametype == GT_CTF || level.gametype == GT_CTY) && item->giType == IT_TEAM) { // Special case for CTF flags
2722 		dropped->think = Team_DroppedFlagThink;
2723 		dropped->nextthink = level.time + 30000;
2724 		Team_CheckDroppedItem( dropped );
2725 
2726 		//rww - so bots know
2727 		if (dropped->item->giTag == PW_REDFLAG)
2728 		{
2729 			droppedRedFlag = dropped;
2730 		}
2731 		else if (dropped->item->giTag == PW_BLUEFLAG)
2732 		{
2733 			droppedBlueFlag = dropped;
2734 		}
2735 	} else { // auto-remove after 30 seconds
2736 		dropped->think = G_FreeEntity;
2737 		dropped->nextthink = level.time + 30000;
2738 	}
2739 
2740 	dropped->flags = FL_DROPPED_ITEM;
2741 
2742 	if (item->giType == IT_WEAPON || item->giType == IT_POWERUP)
2743 	{
2744 		dropped->s.eFlags |= EF_DROPPEDWEAPON;
2745 	}
2746 
2747 	vectoangles(velocity, dropped->s.angles);
2748 	dropped->s.angles[PITCH] = 0;
2749 
2750 	if (item->giTag == WP_TRIP_MINE ||
2751 		item->giTag == WP_DET_PACK)
2752 	{
2753 		dropped->s.angles[PITCH] = -90;
2754 	}
2755 
2756 	if (item->giTag != WP_BOWCASTER &&
2757 		item->giTag != WP_DET_PACK &&
2758 		item->giTag != WP_THERMAL)
2759 	{
2760 		dropped->s.angles[ROLL] = -90;
2761 	}
2762 
2763 	dropped->physicsObject = qtrue;
2764 
2765 	trap->LinkEntity ((sharedEntity_t *)dropped);
2766 
2767 	return dropped;
2768 }
2769 
2770 /*
2771 ================
2772 Drop_Item
2773 
2774 Spawns an item and tosses it forward
2775 ================
2776 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle)2777 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
2778 	vec3_t	velocity;
2779 	vec3_t	angles;
2780 
2781 	VectorCopy( ent->s.apos.trBase, angles );
2782 	angles[YAW] += angle;
2783 	angles[PITCH] = 0;	// always forward
2784 
2785 	AngleVectors( angles, velocity, NULL, NULL );
2786 	VectorScale( velocity, 150, velocity );
2787 	velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
2788 
2789 	return LaunchItem( item, ent->s.pos.trBase, velocity );
2790 }
2791 
2792 
2793 /*
2794 ================
2795 Use_Item
2796 
2797 Respawn the item
2798 ================
2799 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)2800 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2801 	RespawnItem( ent );
2802 }
2803 
2804 //======================================================================
2805 
2806 /*
2807 ================
2808 FinishSpawningItem
2809 
2810 Traces down to find where an item should rest, instead of letting them
2811 free fall from their spawn points
2812 ================
2813 */
FinishSpawningItem(gentity_t * ent)2814 void FinishSpawningItem( gentity_t *ent ) {
2815 	trace_t		tr;
2816 	vec3_t		dest;
2817 //	gitem_t		*item;
2818 
2819 //	VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
2820 //	VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
2821 
2822 	if (level.gametype == GT_SIEGE)
2823 	{ //in siege remove all powerups
2824 		if (ent->item->giType == IT_POWERUP)
2825 		{
2826 			G_FreeEntity(ent);
2827 			return;
2828 		}
2829 	}
2830 
2831 	if (level.gametype != GT_JEDIMASTER)
2832 	{
2833 		if (HasSetSaberOnly())
2834 		{
2835 			if (ent->item->giType == IT_AMMO)
2836 			{
2837 				G_FreeEntity( ent );
2838 				return;
2839 			}
2840 
2841 			if (ent->item->giType == IT_HOLDABLE)
2842 			{
2843 				if (ent->item->giTag == HI_SEEKER ||
2844 					ent->item->giTag == HI_SHIELD ||
2845 					ent->item->giTag == HI_SENTRY_GUN)
2846 				{
2847 					G_FreeEntity( ent );
2848 					return;
2849 				}
2850 			}
2851 		}
2852 	}
2853 	else
2854 	{ //no powerups in jedi master
2855 		if (ent->item->giType == IT_POWERUP)
2856 		{
2857 			G_FreeEntity(ent);
2858 			return;
2859 		}
2860 	}
2861 
2862 	if (level.gametype == GT_HOLOCRON)
2863 	{
2864 		if (ent->item->giType == IT_POWERUP)
2865 		{
2866 			if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
2867 				ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK)
2868 			{
2869 				G_FreeEntity(ent);
2870 				return;
2871 			}
2872 		}
2873 	}
2874 
2875 	if (g_forcePowerDisable.integer)
2876 	{ //if force powers disabled, don't add force powerups
2877 		if (ent->item->giType == IT_POWERUP)
2878 		{
2879 			if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
2880 				ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK ||
2881 				ent->item->giTag == PW_FORCE_BOON)
2882 			{
2883 				G_FreeEntity(ent);
2884 				return;
2885 			}
2886 		}
2887 	}
2888 
2889 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
2890 	{
2891 		if ( ent->item->giType == IT_ARMOR ||
2892 			ent->item->giType == IT_HEALTH ||
2893 			(ent->item->giType == IT_HOLDABLE && (ent->item->giTag == HI_MEDPAC || ent->item->giTag == HI_MEDPAC_BIG)) )
2894 		{
2895 			G_FreeEntity(ent);
2896 			return;
2897 		}
2898 	}
2899 
2900 	if (level.gametype != GT_CTF &&
2901 		level.gametype != GT_CTY &&
2902 		ent->item->giType == IT_TEAM)
2903 	{
2904 		int killMe = 0;
2905 
2906 		switch (ent->item->giTag)
2907 		{
2908 		case PW_REDFLAG:
2909 			killMe = 1;
2910 			break;
2911 		case PW_BLUEFLAG:
2912 			killMe = 1;
2913 			break;
2914 		case PW_NEUTRALFLAG:
2915 			killMe = 1;
2916 			break;
2917 		default:
2918 			break;
2919 		}
2920 
2921 		if (killMe)
2922 		{
2923 			G_FreeEntity( ent );
2924 			return;
2925 		}
2926 	}
2927 
2928 	VectorSet (ent->r.mins, -8, -8, -0);
2929 	VectorSet (ent->r.maxs, 8, 8, 16);
2930 
2931 	ent->s.eType = ET_ITEM;
2932 	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
2933 	ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
2934 
2935 	ent->r.contents = CONTENTS_TRIGGER;
2936 	ent->touch = Touch_Item;
2937 	// useing an item causes it to respawn
2938 	ent->use = Use_Item;
2939 
2940 	// create a Ghoul2 model if the world model is a glm
2941 /*	item = &bg_itemlist[ ent->s.modelindex ];
2942 	if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm"))
2943 	{
2944 		trap->G2API_InitGhoul2Model(&ent->s, item->world_model[0], G_ModelIndex(item->world_model[0] ), 0, 0, 0, 0);
2945 		ent->s.radius = 60;
2946 	}
2947 */
2948 	if ( ent->spawnflags & ITMSF_SUSPEND ) {
2949 		// suspended
2950 		G_SetOrigin( ent, ent->s.origin );
2951 	} else {
2952 		// drop to floor
2953 
2954 		//if it is directly even with the floor it will return startsolid, so raise up by 0.1
2955 		//and temporarily subtract 0.1 from the z maxs so that going up doesn't push into the ceiling
2956 		ent->s.origin[2] += 0.1f;
2957 		ent->r.maxs[2] -= 0.1f;
2958 
2959 		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
2960 		trap->Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID, qfalse, 0, 0 );
2961 		if ( tr.startsolid ) {
2962 			trap->Print ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
2963 			G_FreeEntity( ent );
2964 			return;
2965 		}
2966 
2967 		//add the 0.1 back after the trace
2968 		ent->r.maxs[2] += 0.1f;
2969 
2970 		// allow to ride movers
2971 		ent->s.groundEntityNum = tr.entityNum;
2972 
2973 		G_SetOrigin( ent, tr.endpos );
2974 	}
2975 
2976 	// team slaves and targeted items aren't present at start
2977 	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
2978 		ent->s.eFlags |= EF_NODRAW;
2979 		ent->r.contents = 0;
2980 		return;
2981 	}
2982 
2983 	// powerups don't spawn in for a while
2984 	/*
2985 	if ( ent->item->giType == IT_POWERUP ) {
2986 		float	respawn;
2987 
2988 		respawn = 45 + Q_flrand(-1.0f, 1.0f) * 15;
2989 		ent->s.eFlags |= EF_NODRAW;
2990 		ent->r.contents = 0;
2991 		ent->nextthink = level.time + respawn * 1000;
2992 		ent->think = RespawnItem;
2993 		return;
2994 	}
2995 	*/
2996 
2997 	trap->LinkEntity ((sharedEntity_t *)ent);
2998 }
2999 
3000 
3001 qboolean	itemRegistered[MAX_ITEMS];
3002 
3003 /*
3004 ==================
3005 G_CheckTeamItems
3006 ==================
3007 */
G_CheckTeamItems(void)3008 void G_CheckTeamItems( void ) {
3009 
3010 	// Set up team stuff
3011 	Team_InitGame();
3012 
3013 	if( level.gametype == GT_CTF || level.gametype == GT_CTY ) {
3014 		gitem_t	*item;
3015 
3016 		// check for the two flags
3017 		item = BG_FindItem( "team_CTF_redflag" );
3018 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
3019 			trap->Print( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map\n" );
3020 		}
3021 		item = BG_FindItem( "team_CTF_blueflag" );
3022 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
3023 			trap->Print( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map\n" );
3024 		}
3025 	}
3026 }
3027 
3028 /*
3029 ==============
3030 ClearRegisteredItems
3031 ==============
3032 */
ClearRegisteredItems(void)3033 void ClearRegisteredItems( void ) {
3034 	memset( itemRegistered, 0, sizeof( itemRegistered ) );
3035 
3036 	// players always start with the base weapon
3037 	RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) );
3038 	RegisterItem( BG_FindItemForWeapon( WP_STUN_BATON ) );
3039 	RegisterItem( BG_FindItemForWeapon( WP_MELEE ) );
3040 	RegisterItem( BG_FindItemForWeapon( WP_SABER ) );
3041 
3042 	if (level.gametype == GT_SIEGE)
3043 	{ //kind of cheesy, maybe check if siege class with disp's is gonna be on this map too
3044 		G_PrecacheDispensers();
3045 	}
3046 }
3047 
3048 /*
3049 ===============
3050 RegisterItem
3051 
3052 The item will be added to the precache list
3053 ===============
3054 */
RegisterItem(gitem_t * item)3055 void RegisterItem( gitem_t *item ) {
3056 	if ( !item ) {
3057 		trap->Error( ERR_DROP, "RegisterItem: NULL" );
3058 	}
3059 	itemRegistered[ item - bg_itemlist ] = qtrue;
3060 }
3061 
3062 
3063 /*
3064 ===============
3065 SaveRegisteredItems
3066 
3067 Write the needed items to a config string
3068 so the client will know which ones to precache
3069 ===============
3070 */
SaveRegisteredItems(void)3071 void SaveRegisteredItems( void ) {
3072 	char	string[MAX_ITEMS+1];
3073 	int		i;
3074 	int		count;
3075 
3076 	count = 0;
3077 	for ( i = 0 ; i < bg_numItems ; i++ ) {
3078 		if ( itemRegistered[i] ) {
3079 			count++;
3080 			string[i] = '1';
3081 		} else {
3082 			string[i] = '0';
3083 		}
3084 	}
3085 	string[ bg_numItems ] = 0;
3086 
3087 //	trap->Print( "%i items registered\n", count );
3088 	trap->SetConfigstring(CS_ITEMS, string);
3089 }
3090 
3091 /*
3092 ============
3093 G_ItemDisabled
3094 ============
3095 */
G_ItemDisabled(gitem_t * item)3096 int G_ItemDisabled( gitem_t *item ) {
3097 
3098 	char name[128];
3099 
3100 	Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
3101 	return trap->Cvar_VariableIntegerValue( name );
3102 }
3103 
3104 /*
3105 ============
3106 G_SpawnItem
3107 
3108 Sets the clipping size and plants the object on the floor.
3109 
3110 Items can't be immediately dropped to floor, because they might
3111 be on an entity that hasn't spawned yet.
3112 ============
3113 */
G_SpawnItem(gentity_t * ent,gitem_t * item)3114 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
3115 	int wDisable = 0;
3116 
3117 	G_SpawnFloat( "random", "0", &ent->random );
3118 	G_SpawnFloat( "wait", "0", &ent->wait );
3119 
3120 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
3121 	{
3122 		wDisable = g_duelWeaponDisable.integer;
3123 	}
3124 	else
3125 	{
3126 		wDisable = g_weaponDisable.integer;
3127 	}
3128 
3129 	if (item->giType == IT_WEAPON &&
3130 		wDisable &&
3131 		(wDisable & (1 << item->giTag)))
3132 	{
3133 		if (level.gametype != GT_JEDIMASTER)
3134 		{
3135 			G_FreeEntity( ent );
3136 			return;
3137 		}
3138 	}
3139 
3140 	RegisterItem( item );
3141 	if ( G_ItemDisabled(item) )
3142 		return;
3143 
3144 	ent->item = item;
3145 	// some movers spawn on the second frame, so delay item
3146 	// spawns until the third frame so they can ride trains
3147 	ent->nextthink = level.time + FRAMETIME * 2;
3148 	ent->think = FinishSpawningItem;
3149 
3150 	ent->physicsBounce = 0.50;		// items are bouncy
3151 
3152 	if ( item->giType == IT_POWERUP ) {
3153 		G_SoundIndex( "sound/items/respawn1" );
3154 		G_SpawnFloat( "noglobalsound", "0", &ent->speed);
3155 	}
3156 }
3157 
3158 
3159 /*
3160 ================
3161 G_BounceItem
3162 
3163 ================
3164 */
G_BounceItem(gentity_t * ent,trace_t * trace)3165 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
3166 	vec3_t	velocity;
3167 	float	dot;
3168 	int		hitTime;
3169 
3170 	// reflect the velocity on the trace plane
3171 	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
3172 	BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
3173 	dot = DotProduct( velocity, trace->plane.normal );
3174 	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
3175 
3176 	// cut the velocity to keep from bouncing forever
3177 	VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
3178 
3179 	if ((ent->s.weapon == WP_DET_PACK && ent->s.eType == ET_GENERAL && ent->physicsObject))
3180 	{ //detpacks only
3181 		if (ent->touch)
3182 		{
3183 			ent->touch(ent, &g_entities[trace->entityNum], trace);
3184 			return;
3185 		}
3186 	}
3187 
3188 	// check for stop
3189 	if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
3190 		trace->endpos[2] += 1.0;	// make sure it is off ground
3191 		SnapVector( trace->endpos );
3192 		G_SetOrigin( ent, trace->endpos );
3193 		ent->s.groundEntityNum = trace->entityNum;
3194 		return;
3195 	}
3196 
3197 	VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
3198 	VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
3199 	ent->s.pos.trTime = level.time;
3200 
3201 	if (ent->s.eType == ET_HOLOCRON ||
3202 		(ent->s.shouldtarget && ent->s.eType == ET_GENERAL && ent->physicsObject))
3203 	{ //holocrons and sentry guns
3204 		if (ent->touch)
3205 		{
3206 			ent->touch(ent, &g_entities[trace->entityNum], trace);
3207 		}
3208 	}
3209 }
3210 
3211 
3212 /*
3213 ================
3214 G_RunItem
3215 
3216 ================
3217 */
G_RunItem(gentity_t * ent)3218 void G_RunItem( gentity_t *ent ) {
3219 	vec3_t		origin;
3220 	trace_t		tr;
3221 	int			contents;
3222 	int			mask;
3223 
3224 	// if groundentity has been set to ENTITYNUM_NONE, it may have been pushed off an edge
3225 	if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) {
3226 		if ( ent->s.pos.trType != TR_GRAVITY ) {
3227 			ent->s.pos.trType = TR_GRAVITY;
3228 			ent->s.pos.trTime = level.time;
3229 		}
3230 	}
3231 
3232 	if ( ent->s.pos.trType == TR_STATIONARY ) {
3233 		// check think function
3234 		G_RunThink( ent );
3235 		return;
3236 	}
3237 
3238 	// get current position
3239 	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
3240 
3241 	// trace a line from the previous position to the current position
3242 	if ( ent->clipmask ) {
3243 		mask = ent->clipmask;
3244 	} else {
3245 		mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
3246 	}
3247 	trap->Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->r.ownerNum, mask, qfalse, 0, 0 );
3248 
3249 	VectorCopy( tr.endpos, ent->r.currentOrigin );
3250 
3251 	if ( tr.startsolid ) {
3252 
3253 		tr.fraction = 0;
3254 	}
3255 
3256 	trap->LinkEntity( (sharedEntity_t *)ent );	// FIXME: avoid this for stationary?
3257 
3258 	// check think function
3259 	G_RunThink( ent );
3260 
3261 	if ( tr.fraction == 1 ) {
3262 		return;
3263 	}
3264 
3265 	// if it is in a nodrop volume, remove it
3266 	contents = trap->PointContents( ent->r.currentOrigin, -1 );
3267 	if ( contents & CONTENTS_NODROP ) {
3268 		if (ent->item && ent->item->giType == IT_TEAM) {
3269 			Team_FreeEntity(ent);
3270 		} else if(ent->genericValue15 == HI_SENTRY_GUN) {
3271 			turret_free(ent);
3272 		} else {
3273 			G_FreeEntity( ent );
3274 		}
3275 		return;
3276 	}
3277 
3278 	G_BounceItem( ent, &tr );
3279 }
3280 
3281