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 "g_functions.h"
26 #include "g_items.h"
27 #include "wp_saber.h"
28 #include "../cgame/cg_local.h"
29 #include "b_local.h"
30 
31 extern qboolean	missionInfo_Updated;
32 
33 extern void CrystalAmmoSettings(gentity_t *ent);
34 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
35 extern qboolean PM_InKnockDown( playerState_t *ps );
36 extern qboolean PM_InGetUp( playerState_t *ps );
37 extern void WP_SetSaber( gentity_t *ent, int saberNum, const char *saberName );
38 extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
39 extern void WP_SaberFallSound( gentity_t *owner, gentity_t *saber );
40 extern saber_colors_t TranslateSaberColor( const char *name );
41 
42 extern	cvar_t	*g_spskill;
43 extern	cvar_t	*g_sex;
44 extern cvar_t	*g_saberPickuppableDroppedSabers;
45 
46 #define MAX_BACTA_HEAL_AMOUNT		25
47 
48 /*
49 
50   Items are any object that a player can touch to gain some effect.
51 
52   Pickup will return the number of seconds until they should respawn.
53 
54   all items should pop when dropped in lava or slime
55 
56   Respawnable items don't actually go away when picked up, they are
57   just made invisible and untouchable.  This allows them to ride
58   movers and respawn apropriately.
59 */
60 
61 // Item Spawn flags
62 #define ITMSF_SUSPEND		1
63 #define ITMSF_NOPLAYER		2
64 #define ITMSF_ALLOWNPC		4
65 #define ITMSF_NOTSOLID		8
66 #define ITMSF_VERTICAL		16
67 #define ITMSF_INVISIBLE		32
68 #define ITMSF_NOGLOW		64
69 #define ITMSF_USEPICKUP		128
70 #define ITMSF_STATIONARY	2048
71 
72 //======================================================================
73 
74 /*
75 ===============
76 G_InventorySelectable
77 ===============
78 */
G_InventorySelectable(int index,gentity_t * other)79 qboolean G_InventorySelectable( int index,gentity_t *other)
80 {
81 	if (other->client->ps.inventory[index])
82 	{
83 		return qtrue;
84 	}
85 
86 	return qfalse;
87 }
88 
89 extern qboolean INV_GoodieKeyGive( gentity_t *target );
90 extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
Pickup_Holdable(gentity_t * ent,gentity_t * other)91 int Pickup_Holdable( gentity_t *ent, gentity_t *other )
92 {
93 	int		i,original;
94 
95 	other->client->ps.stats[STAT_ITEMS] |= (1<<ent->item->giTag);
96 
97 	if ( ent->item->giTag == INV_SECURITY_KEY )
98 	{//give the key
99 		//FIXME: temp message
100 		gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SECURITY_KEY" );
101 		INV_SecurityKeyGive( other, ent->message );
102 	}
103 	else if ( ent->item->giTag == INV_GOODIE_KEY )
104 	{//give the key
105 		//FIXME: temp message
106 		gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SUPPLY_KEY" );
107 		INV_GoodieKeyGive( other );
108 	}
109 	else
110 	{// Picking up a normal item?
111 		other->client->ps.inventory[ent->item->giTag]++;
112 	}
113 	// Got a security key
114 
115 	// Set the inventory select, just in case it hasn't
116 	original = cg.inventorySelect;
117 	for ( i = 0 ; i < INV_MAX ; i++ )
118 	{
119 		if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
120 		{
121 			cg.inventorySelect = (INV_MAX - 1);
122 		}
123 
124 		if ( G_InventorySelectable( cg.inventorySelect,other ) )
125 		{
126 			return 60;
127 		}
128 		cg.inventorySelect++;
129 	}
130 
131 	cg.inventorySelect = original;
132 
133 	return 60;
134 }
135 
136 
137 //======================================================================
Add_Ammo2(gentity_t * ent,int ammoType,int count)138 int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
139 {
140 
141 	if (ammoType != AMMO_FORCE)
142 	{
143 		ent->client->ps.ammo[ammoType] += count;
144 
145 		// since the ammo is the weapon in this case, picking up ammo should actually give you the weapon
146 		switch( ammoType )
147 		{
148 		case AMMO_THERMAL:
149 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL );
150 			break;
151 		case AMMO_DETPACK:
152 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK );
153 			break;
154 		case AMMO_TRIPMINE:
155 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE );
156 			break;
157 		}
158 
159 		if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
160 		{
161 			ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
162 			return qfalse;
163 		}
164 	}
165 	else
166 	{
167 		if ( ent->client->ps.forcePower >= ammoData[ammoType].max )
168 		{//if have full force, just get 25 extra per crystal
169 			ent->client->ps.forcePower += 25;
170 		}
171 		else
172 		{//else if don't have full charge, give full amount, up to max + 25
173 			ent->client->ps.forcePower += count;
174 			if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 )
175 			{//cap at max + 25
176 				ent->client->ps.forcePower = ammoData[ammoType].max + 25;
177 			}
178 		}
179 
180 		if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 )
181 		{//always cap at twice a full charge
182 			ent->client->ps.forcePower = ammoData[ammoType].max*2;
183 			return qfalse;		// can't hold any more
184 		}
185 	}
186 	return qtrue;
187 }
188 
189 //-------------------------------------------------------
Add_Ammo(gentity_t * ent,int weapon,int count)190 void Add_Ammo (gentity_t *ent, int weapon, int count)
191 {
192 	Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
193 }
194 
195 //-------------------------------------------------------
Pickup_Ammo(gentity_t * ent,gentity_t * other)196 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
197 {
198 	int		quantity;
199 
200 	if ( ent->count ) {
201 		quantity = ent->count;
202 	} else {
203 		quantity = ent->item->quantity;
204 	}
205 
206 	Add_Ammo2 (other, ent->item->giTag, quantity);
207 
208 	return 30;
209 }
210 
211 //======================================================================
Add_Batteries(gentity_t * ent,int * count)212 void Add_Batteries( gentity_t *ent, int *count )
213 {
214 	if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count )
215 	{
216 		if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES )
217 		{
218 			// steal what we need, then leave the rest for later
219 			*count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge );
220 			ent->client->ps.batteryCharge = MAX_BATTERIES;
221 		}
222 		else
223 		{
224 			// just drain all of the batteries
225 			ent->client->ps.batteryCharge += *count;
226 			*count = 0;
227 		}
228 
229 		G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 );
230 	}
231 }
232 
233 //-------------------------------------------------------
Pickup_Battery(gentity_t * ent,gentity_t * other)234 int Pickup_Battery( gentity_t *ent, gentity_t *other )
235 {
236 	int	quantity;
237 
238 	if ( ent->count )
239 	{
240 		quantity = ent->count;
241 	}
242 	else
243 	{
244 		quantity = ent->item->quantity;
245 	}
246 
247 	// There may be some left over in quantity if the player is close to full, but with pickup items, this amount will just be lost
248 	Add_Batteries( other, &quantity );
249 
250 	return 30;
251 }
252 
253 //======================================================================
254 
G_CopySaberItemValues(gentity_t * pickUpSaber,gentity_t * oldSaber)255 void G_CopySaberItemValues( gentity_t *pickUpSaber, gentity_t *oldSaber )
256 {
257 	if ( oldSaber && pickUpSaber )
258 	{
259 		oldSaber->spawnflags = pickUpSaber->spawnflags;
260 		oldSaber->random = pickUpSaber->random;
261 		oldSaber->flags = pickUpSaber->flags;
262 	}
263 }
264 
G_DropSaberItem(const char * saberType,saber_colors_t saberColor,vec3_t saberPos,vec3_t saberVel,vec3_t saberAngles,gentity_t * copySaber)265 gentity_t *G_DropSaberItem( const char *saberType, saber_colors_t saberColor, vec3_t saberPos, vec3_t saberVel, vec3_t saberAngles, gentity_t *copySaber )
266 {//turn it into a pick-uppable item!
267 	gentity_t *newItem = NULL;
268 	if ( saberType
269 		&& saberType[0] )
270 	{//have a valid string to use for saberType
271 		newItem = G_Spawn();
272 		if ( newItem )
273 		{
274 			newItem->classname = G_NewString( "weapon_saber" );
275 			VectorCopy( saberPos, newItem->s.origin );
276 			G_SetOrigin( newItem, newItem->s.origin );
277 			VectorCopy( saberAngles, newItem->s.angles );
278 			G_SetAngles( newItem, newItem->s.angles );
279 			newItem->spawnflags = 128;/*ITMSF_USEPICKUP*/
280 			newItem->spawnflags |= 64;/*ITMSF_NOGLOW*/
281 			newItem->NPC_type = G_NewString( saberType );//saberType
282 			//FIXME: transfer per-blade color somehow?
283 			newItem->NPC_targetname = (char *)saberColorStringForColor[saberColor];
284 			newItem->count = 1;
285 			newItem->flags = FL_DROPPED_ITEM;
286 			G_SpawnItem( newItem, FindItemForWeapon( WP_SABER ) );
287 			newItem->s.pos.trType = TR_GRAVITY;
288 			newItem->s.pos.trTime = level.time;
289 			VectorCopy( saberVel, newItem->s.pos.trDelta );
290 			//newItem->s.eFlags |= EF_BOUNCE_HALF;
291 			//copy some values from another saber, if provided:
292 			G_CopySaberItemValues( copySaber, newItem );
293 			//don't *think* about calling FinishSpawningItem, just do it!
294 			newItem->e_ThinkFunc = thinkF_NULL;
295 			newItem->nextthink = -1;
296 			FinishSpawningItem( newItem );
297 			newItem->delay = level.time + 500;//so you can't pick it back up right away
298 		}
299 	}
300 	return newItem;
301 }
302 
303 extern void G_SetSabersFromCVars( gentity_t *ent );
Pickup_Saber(gentity_t * self,qboolean hadSaber,gentity_t * pickUpSaber)304 qboolean Pickup_Saber( gentity_t *self, qboolean hadSaber, gentity_t *pickUpSaber )
305 {
306 	//NOTE: loopAnim = saberSolo, alt_fire = saberLeftHand, NPC_type = saberType, NPC_targetname = saberColor
307 	qboolean foundIt = qfalse;
308 
309 	if ( !pickUpSaber || !self || !self->client )
310 	{
311 		return qfalse;
312 	}
313 
314 	//G_RemoveWeaponModels( ent );//???
315 	if ( Q_stricmp( "player", pickUpSaber->NPC_type ) == 0 )
316 	{//"player" means use cvar info
317 		G_SetSabersFromCVars( self );
318 		foundIt = qtrue;
319 	}
320 	else
321 	{
322 		saberInfo_t	newSaber={0};
323 		qboolean swapSabers = qfalse;
324 
325 		if ( self->client->ps.weapon == WP_SABER
326 			&& self->client->ps.weaponTime > 0 )
327 		{//can't pick up a new saber while the old one is busy (also helps to work as a debouncer so you don't swap out sabers rapidly when touching more than one at a time)
328 			return qfalse;
329 		}
330 
331 		if ( pickUpSaber->count == 1
332 			&& g_saberPickuppableDroppedSabers->integer )
333 		{
334 			swapSabers = qtrue;
335 		}
336 
337 		if ( WP_SaberParseParms( pickUpSaber->NPC_type, &newSaber ) )
338 		{//successfully found a saber .sab entry to use
339 			int	saberNum = 0;
340 			qboolean removeLeftSaber = qfalse;
341 			if ( pickUpSaber->alt_fire )
342 			{//always go in the left hand
343 				if ( !hadSaber )
344 				{//can't have a saber only in your left hand!
345 					return qfalse;
346 				}
347 				saberNum = 1;
348 				//just in case...
349 				removeLeftSaber = qtrue;
350 			}
351 			else if ( !hadSaber )
352 			{//don't have a saber at all yet, put it in our right hand
353 				saberNum = 0;
354 				//just in case...
355 				removeLeftSaber = qtrue;
356 			}
357 			else if ( pickUpSaber->loopAnim//only supposed to use this one saber when grab this pickup
358 				|| (newSaber.saberFlags&SFL_TWO_HANDED) //new saber is two-handed
359 				|| (hadSaber && (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)) )//old saber is two-handed
360 			{//replace the old right-hand saber and remove the left hand one
361 				saberNum = 0;
362 				removeLeftSaber = qtrue;
363 			}
364 			else
365 			{//have, at least, a saber in our right hand and the new one could go in either left or right hand
366 				if ( self->client->ps.dualSabers )
367 				{//I already have 2 sabers
368 					vec3_t dir2Saber, rightDir;
369 					//to determine which one to replace, see which side of me it's on
370 					VectorSubtract( pickUpSaber->currentOrigin, self->currentOrigin, dir2Saber );
371 					dir2Saber[2] = 0;
372 					AngleVectors( self->currentAngles, NULL, rightDir, NULL );
373 					rightDir[2] = 0;
374 					if ( DotProduct( rightDir, dir2Saber ) > 0 )
375 					{
376 						saberNum = 0;
377 					}
378 					else
379 					{
380 						saberNum = 1;
381 						//just in case...
382 						removeLeftSaber = qtrue;
383 					}
384 				}
385 				else
386 				{//just add it as a second saber
387 					saberNum = 1;
388 					//just in case...
389 					removeLeftSaber = qtrue;
390 				}
391 			}
392 			if ( saberNum == 0 )
393 			{//want to reach out with right hand
394 				if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD )
395 				{//but only if already playing the pickup with left hand anim...
396 					NPC_SetAnim( self, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
397 				}
398 				if ( swapSabers )
399 				{//drop first one where the one we're picking up is
400 					G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
401 					if ( removeLeftSaber )
402 					{//drop other one at my origin
403 						G_DropSaberItem( self->client->ps.saber[1].name, self->client->ps.saber[1].blade[0].color, self->currentOrigin, (float *)vec3_origin, self->currentAngles, pickUpSaber );
404 					}
405 				}
406 			}
407 			else
408 			{
409 				if ( swapSabers )
410 				{
411 					G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
412 				}
413 			}
414 			if ( removeLeftSaber )
415 			{
416 				WP_RemoveSaber( self, 1 );
417 			}
418 			WP_SetSaber( self, saberNum, pickUpSaber->NPC_type );
419 			WP_SaberInitBladeData( self );
420 			if ( self->client->ps.saber[saberNum].stylesLearned )
421 			{
422 				self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].stylesLearned;
423 			}
424 			if ( self->client->ps.saber[saberNum].singleBladeStyle )
425 			{
426 				self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].singleBladeStyle;
427 			}
428 			if ( pickUpSaber->NPC_targetname != NULL )
429 			{//NPC_targetname = saberColor
430 				saber_colors_t saber_color = TranslateSaberColor( pickUpSaber->NPC_targetname );
431 				for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
432 				{
433 					self->client->ps.saber[saberNum].blade[bladeNum].color = saber_color;
434 				}
435 			}
436 			if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD
437 				|| self->client->ps.torsoAnim == BOTH_SABERPULL )
438 			{//don't let them attack right away, force them to finish the anim
439 				self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
440 			}
441 			foundIt = qtrue;
442 		}
443 		WP_SaberFreeStrings(newSaber);
444 	}
445 	return foundIt;
446 }
447 
448 extern void CG_ChangeWeapon( int num );
Pickup_Weapon(gentity_t * ent,gentity_t * other)449 int Pickup_Weapon (gentity_t *ent, gentity_t *other)
450 {
451 	int		quantity;
452 	qboolean	hadWeapon = qfalse;
453 
454 	/*
455 	if ( ent->count || (ent->activator && !ent->activator->s.number) )
456 	{
457 		quantity = ent->count;
458 	}
459 	else
460 	{
461 		quantity = ent->item->quantity;
462 	}
463 	*/
464 
465 	// dropped items are always picked up
466 	if ( ent->flags & FL_DROPPED_ITEM )
467 	{
468 		quantity = ent->count;
469 	}
470 	else
471 	{//wasn't dropped
472 		quantity = ent->item->quantity?ent->item->quantity:50;
473 	}
474 
475 	// add the weapon
476 	if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
477 	{
478 		hadWeapon = qtrue;
479 	}
480 	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
481 
482 	if ( ent->item->giTag == WP_SABER && (!hadWeapon || ent->NPC_type != NULL) )
483 	{//didn't have a saber or it is specifying a certain kind of saber to use
484 		if ( !Pickup_Saber( other, hadWeapon, ent ) )
485 		{
486 			return 0;
487 		}
488 	}
489 
490 	if ( other->s.number )
491 	{//NPC
492 		if ( other->s.weapon == WP_NONE
493 			|| ent->item->giTag == WP_SABER )
494 		{//NPC with no weapon picked up a weapon, change to this weapon
495 			//FIXME: clear/set the alt-fire flag based on the picked up weapon and my class?
496 			other->client->ps.weapon = ent->item->giTag;
497 			other->client->ps.weaponstate = WEAPON_RAISING;
498 			ChangeWeapon( other, ent->item->giTag );
499 			if ( ent->item->giTag == WP_SABER )
500 			{
501 				other->client->ps.SaberActivate();
502 				WP_SaberAddG2SaberModels( other );
503 			}
504 			else
505 			{
506 				G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl, other->handRBolt, 0 );
507 			}
508 		}
509 	}
510 	if ( ent->item->giTag == WP_SABER )
511 	{//picked up a saber
512 		if ( other->s.weapon != WP_SABER )
513 		{//player picking up saber
514 			other->client->ps.weapon = WP_SABER;
515 			other->client->ps.weaponstate = WEAPON_RAISING;
516 			if ( other->s.number < MAX_CLIENTS )
517 			{//make sure the cgame-side knows this
518 				CG_ChangeWeapon( WP_SABER );
519 			}
520 			else
521 			{//make sure the cgame-side knows this
522 				ChangeWeapon( other, WP_SABER );
523 			}
524 		}
525 		if ( !other->client->ps.SaberActive() )
526 		{//turn it/them on!
527 			other->client->ps.SaberActivate();
528 		}
529 	}
530 
531 	if ( quantity )
532 	{
533 		// Give ammo
534 		Add_Ammo( other, ent->item->giTag, quantity );
535 	}
536 	return 5;
537 }
538 
539 
540 //======================================================================
541 
ITM_AddHealth(gentity_t * ent,int count)542 int ITM_AddHealth (gentity_t *ent, int count)
543 {
544 
545 	ent->health += count;
546 
547 	if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])	// Past max health
548 	{
549 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
550 
551 		return qfalse;
552 	}
553 
554 	return qtrue;
555 
556 }
557 
Pickup_Health(gentity_t * ent,gentity_t * other)558 int Pickup_Health (gentity_t *ent, gentity_t *other) {
559 	int			max;
560 	int			quantity;
561 
562 	max = other->client->ps.stats[STAT_MAX_HEALTH];
563 
564 	if ( ent->count ) {
565 		quantity = ent->count;
566 	} else {
567 		quantity = ent->item->quantity;
568 	}
569 
570 	other->health += quantity;
571 
572 	if (other->health > max ) {
573 		other->health = max;
574 	}
575 
576 	if ( ent->item->giTag == 100 ) {		// mega health respawns slow
577 		return 120;
578 	}
579 
580 	return 30;
581 }
582 
583 //======================================================================
584 
ITM_AddArmor(gentity_t * ent,int count)585 int ITM_AddArmor (gentity_t *ent, int count)
586 {
587 
588 	ent->client->ps.stats[STAT_ARMOR] += count;
589 
590 	if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
591 	{
592 		ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
593 		return qfalse;
594 	}
595 
596 	return qtrue;
597 }
598 
599 
Pickup_Armor(gentity_t * ent,gentity_t * other)600 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
601 
602 	// make sure that the shield effect is on
603 	other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
604 
605 	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
606 	if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) {
607 		other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
608 	}
609 
610 	return 30;
611 }
612 
613 
614 
615 //======================================================================
616 
Pickup_Holocron(gentity_t * ent,gentity_t * other)617 int Pickup_Holocron( gentity_t *ent, gentity_t *other )
618 {
619 	int forcePower = ent->item->giTag;
620 	int forceLevel = ent->count;
621 	// check if out of range
622 	if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS )
623 	{
624 		gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel );
625 		return 1;
626 	}
627 
628 	// don't pick up if already known AND your level is higher than pickup level
629 	if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) )
630 	{
631 		//don't pickup if item is lower than current level
632 		if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel )
633 		{
634 			return 1;
635 		}
636 	}
637 
638 	other->client->ps.forcePowerLevel[forcePower] = forceLevel;
639 	other->client->ps.forcePowersKnown |= ( 1 << forcePower );
640 
641 	missionInfo_Updated = qtrue;	// Activate flashing text
642 	gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine.
643 	cg_updatedDataPadForcePower1.integer = forcePower+1;
644 	gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine.
645 	cg_updatedDataPadForcePower2.integer = 0;
646 	gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine.
647 	cg_updatedDataPadForcePower3.integer = 0;
648 
649 	return 1;
650 }
651 
652 
653 //======================================================================
654 
655 /*
656 ===============
657 RespawnItem
658 ===============
659 */
RespawnItem(gentity_t * ent)660 void RespawnItem( gentity_t *ent ) {
661 }
662 
663 
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)664 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
665 {
666 	if ( !item->item ) {
667 		return qfalse;
668 	}
669 	if ( item->item->giType == IT_HOLDABLE &&
670 		item->item->giTag == INV_SECURITY_KEY ) {
671 		return qfalse;
672 	}
673 	if ( (item->flags&FL_DROPPED_ITEM)
674 		&& item->activator != &g_entities[0]
675 		&& pickerupper->s.number
676 		&& pickerupper->s.weapon == WP_NONE
677 		&& pickerupper->enemy
678 		&& pickerupper->painDebounceTime < level.time
679 		&& pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
680 		&& !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) ) // not being forced to march
681 	{//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
682 		if ( level.time - item->s.time < 3000 )//was 5000
683 		{
684 			return qfalse;
685 		}
686 		return qtrue;
687 	}
688 	return qfalse;
689 }
690 
G_CanPickUpWeapons(gentity_t * other)691 qboolean G_CanPickUpWeapons( gentity_t *other )
692 {
693 	if ( !other || !other->client )
694 	{
695 		return qfalse;
696 	}
697 	switch ( other->client->NPC_class )
698 	{
699 	case CLASS_ATST:
700 	case CLASS_GONK:
701 	case CLASS_MARK1:
702 	case CLASS_MARK2:
703 	case CLASS_MOUSE:
704 	case CLASS_PROBE:
705 	case CLASS_PROTOCOL:
706 	case CLASS_R2D2:
707 	case CLASS_R5D2:
708 	case CLASS_SEEKER:
709 	case CLASS_REMOTE:
710 	case CLASS_RANCOR:
711 	case CLASS_WAMPA:
712 	case CLASS_JAWA: //FIXME: in some cases it's okay?
713 	case CLASS_UGNAUGHT: //FIXME: in some cases it's okay?
714 	case CLASS_SENTRY:
715 		return qfalse;
716 		break;
717 	default:
718 		break;
719 	}
720 	return qtrue;
721 }
722 /*
723 ===============
724 Touch_Item
725 ===============
726 */
727 extern cvar_t		*g_timescale;
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)728 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
729 	int			respawn = 0;
730 
731 	if (!other->client)
732 		return;
733 	if (other->health < 1)
734 		return;		// dead people can't pickup
735 
736 	if ( other->client->ps.pm_time > 0 )
737 	{//cant pick up when out of control
738 		return;
739 	}
740 
741 	// NPCs can pick it up
742 	if ((ent->spawnflags &  ITMSF_ALLOWNPC) && (!other->s.number))
743 	{
744 		return;
745 	}
746 
747 	// Players cannot pick it up
748 	if ( (ent->spawnflags &  ITMSF_NOPLAYER) && (other->s.number) )
749 	{
750 		return;
751 	}
752 
753 	if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam )
754 	{//only one team can pick it up
755 		return;
756 	}
757 
758 	if ( !G_CanPickUpWeapons( other ) )
759 	{//FIXME: some flag would be better
760 		//droids can't pick up items/weapons!
761 		return;
762 	}
763 
764 	//FIXME: need to make them run toward a dropped weapon when fleeing without one?
765 	//FIXME: need to make them come out of flee mode when pick up their old weapon?
766 	if ( CheckItemCanBePickedUpByNPC( ent, other ) )
767 	{
768 		if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent )
769 		{//they were running to pick me up, they did, so clear goal
770 			other->NPC->goalEntity	= NULL;
771 			other->NPC->squadState	= SQUAD_STAND_AND_SHOOT;
772  			NPCInfo->tempBehavior	= BS_DEFAULT;
773 			TIMER_Set(other, "flee", -1);
774 		}
775 		else
776 		{
777 			return;
778 		}
779 	}
780 	else if ( !(ent->spawnflags &  ITMSF_ALLOWNPC) )
781 	{// NPCs cannot pick it up
782 		if ( other->s.number != 0 )
783 		{// Not the player?
784 			return;
785 		}
786 	}
787 
788 	// the same pickup rules are used for client side and server side
789 	if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
790 		return;
791 	}
792 
793 	if ( other->client )
794 	{
795 		if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) )
796 		{//can't pick up anything while being gripped
797 			return;
798 		}
799 		if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) )
800 		{//can't pick up while in a knockdown
801 			return;
802 		}
803 	}
804 	if (!ent->item) {		//not an item!
805 		gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
806 		return;
807 	}
808 
809 	if ( ent->item->giType == IT_WEAPON
810 		&& ent->item->giTag == WP_SABER )
811 	{//a saber
812 		if ( ent->delay > level.time )
813 		{//just picked it up, don't pick up again right away
814 			return;
815 		}
816 	}
817 
818 	if ( other->s.number < MAX_CLIENTS
819 		&& (ent->spawnflags&ITMSF_USEPICKUP) )
820 	{//only if player is holing use button
821 		if ( !(other->client->usercmd.buttons&BUTTON_USE) )
822 		{//not holding use?
823 			return;
824 		}
825 	}
826 
827 	qboolean bHadWeapon = qfalse;
828 	// call the item-specific pickup function
829 	switch( ent->item->giType )
830 	{
831 	case IT_WEAPON:
832 		if ( other->NPC && other->s.weapon == WP_NONE )
833 		{//Make them duck and sit here for a few seconds
834 			int pickUpTime = Q_irand( 1000, 3000 );
835 			TIMER_Set( other, "duck", pickUpTime );
836 			TIMER_Set( other, "roamTime", pickUpTime );
837 			TIMER_Set( other, "stick", pickUpTime );
838 			TIMER_Set( other, "verifyCP", pickUpTime );
839 			TIMER_Set( other, "attackDelay", 600 );
840 			respawn = 0;
841 		}
842 		if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
843 		{
844 			bHadWeapon = qtrue;
845 		}
846 		respawn = Pickup_Weapon(ent, other);
847 		break;
848 	case IT_AMMO:
849 		respawn = Pickup_Ammo(ent, other);
850 		break;
851 	case IT_ARMOR:
852 		respawn = Pickup_Armor(ent, other);
853 		break;
854 	case IT_HEALTH:
855 		respawn = Pickup_Health(ent, other);
856 		break;
857 	case IT_HOLDABLE:
858 		respawn = Pickup_Holdable(ent, other);
859 		break;
860 	case IT_BATTERY:
861 		respawn = Pickup_Battery( ent, other );
862 		break;
863 	case IT_HOLOCRON:
864 		respawn = Pickup_Holocron( ent, other );
865 		break;
866 	default:
867 		return;
868 	}
869 
870 	if ( !respawn )
871 	{
872 		return;
873 	}
874 
875 	// play the normal pickup sound
876 	if ( !other->s.number && g_timescale->value < 1.0f  )
877 	{//SIGH... with timescale on, you lose events left and right
878 extern void CG_ItemPickup( int itemNum, qboolean bHadItem );
879 		// but we're SP so we'll cheat
880 		cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO,	cgi_S_RegisterSound( ent->item->pickup_sound ) );
881 		// show icon and name on status bar
882 		CG_ItemPickup( ent->s.modelindex, bHadWeapon );
883 	}
884 	else
885 	{
886 		if ( bHadWeapon )
887 		{
888 			G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex );
889 		}
890 		else
891 		{
892 			G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
893 		}
894 	}
895 
896 	// fire item targets
897 	G_UseTargets (ent, other);
898 
899 	if ( ent->item->giType == IT_WEAPON
900 		&& ent->item->giTag == WP_SABER )
901 	{//a saber that was picked up
902 		if ( ent->count < 0 )
903 		{//infinite supply
904 			ent->delay = level.time + 500;
905 			return;
906 		}
907 		ent->count--;
908 		if ( ent->count > 0 )
909 		{//still have more to pick up
910 			ent->delay = level.time + 500;
911 			return;
912 		}
913 	}
914 	// wait of -1 will not respawn
915 //	if ( ent->wait == -1 )
916 	{
917 		//why not just remove me?
918 		G_FreeEntity( ent );
919 		/*
920 		//NOTE: used to do this:  (for respawning?)
921 		ent->svFlags |= SVF_NOCLIENT;
922 		ent->s.eFlags |= EF_NODRAW;
923 		ent->contents = 0;
924 		ent->unlinkAfterEvent = qtrue;
925 		*/
926 		return;
927 	}
928 }
929 
930 
931 //======================================================================
932 
933 /*
934 ================
935 LaunchItem
936 
937 Spawns an item and tosses it forward
938 ================
939 */
LaunchItem(gitem_t * item,const vec3_t origin,const vec3_t velocity,char * target)940 gentity_t *LaunchItem( gitem_t *item, const vec3_t origin, const vec3_t velocity, char *target ) {
941 	gentity_t	*dropped;
942 
943 	dropped = G_Spawn();
944 
945 	dropped->s.eType = ET_ITEM;
946 	dropped->s.modelindex = item - bg_itemlist;	// store item number in modelindex
947 	dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
948 
949 	dropped->classname = G_NewString(item->classname);	//copy it so it can be freed safely
950 	dropped->item = item;
951 
952 	// try using the "correct" mins/maxs first
953 	VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] );
954 	VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] );
955 
956 	if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) &&
957 		(!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2]))
958 	{
959 		VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
960 		VectorScale( dropped->maxs, -1, dropped->mins );
961 	}
962 
963 	dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
964 
965 	if ( target && target[0] )
966 	{
967 		dropped->target = G_NewString( target );
968 	}
969 	else
970 	{
971 		// if not targeting something, auto-remove after 30 seconds
972 		// only if it's NOT a security or goodie key
973 		if (dropped->item->giTag != INV_SECURITY_KEY )
974 		{
975 			dropped->e_ThinkFunc = thinkF_G_FreeEntity;
976 			dropped->nextthink = level.time + 30000;
977 		}
978 
979 		if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE )
980 		{
981 			dropped->nextthink = -1;
982 			dropped->e_ThinkFunc = thinkF_NULL;
983 		}
984 	}
985 
986 	dropped->e_TouchFunc = touchF_Touch_Item;
987 
988 	if ( item->giType == IT_WEAPON )
989 	{
990 		// give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster
991 		if ( item->giTag != WP_BOWCASTER
992 			&& item->giTag != WP_THERMAL
993 			&& item->giTag != WP_TRIP_MINE
994 			&& item->giTag != WP_DET_PACK )
995 		{
996 			VectorSet( dropped->s.angles, 0, Q_flrand(-1.0f, 1.0f) * 180, 90.0f );
997 			G_SetAngles( dropped, dropped->s.angles );
998 		}
999 	}
1000 
1001 	G_SetOrigin( dropped, origin );
1002 	dropped->s.pos.trType = TR_GRAVITY;
1003 	dropped->s.pos.trTime = level.time;
1004 	VectorCopy( velocity, dropped->s.pos.trDelta );
1005 
1006 	dropped->s.eFlags |= EF_BOUNCE_HALF;
1007 
1008 	dropped->flags = FL_DROPPED_ITEM;
1009 
1010 	gi.linkentity (dropped);
1011 
1012 	return dropped;
1013 }
1014 
1015 /*
1016 ================
1017 Drop_Item
1018 
1019 Spawns an item and tosses it forward
1020 ================
1021 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle,qboolean copytarget)1022 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) {
1023 	gentity_t	*dropped = NULL;
1024 	vec3_t	velocity;
1025 	vec3_t	angles;
1026 
1027 	VectorCopy( ent->s.apos.trBase, angles );
1028 	angles[YAW] += angle;
1029 	angles[PITCH] = 0;	// always forward
1030 
1031 	AngleVectors( angles, velocity, NULL, NULL );
1032 	VectorScale( velocity, 150, velocity );
1033 	velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
1034 
1035 	if ( copytarget )
1036 	{
1037 		dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
1038 	}
1039 	else
1040 	{
1041 		dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
1042 	}
1043 
1044 	dropped->activator = ent;//so we know who we belonged to so they can pick it back up later
1045 	dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us
1046 	return dropped;
1047 }
1048 
1049 
1050 /*
1051 ================
1052 Use_Item
1053 
1054 Respawn the item
1055 ================
1056 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)1057 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
1058 {
1059 	if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number )
1060 	{//used directly by the player, pick me up
1061 		if ( (ent->spawnflags&ITMSF_USEPICKUP) )
1062 		{//player has to be touching me and hit use to pick it up, so don't allow this
1063 			if ( !G_BoundsOverlap( ent->absmin, ent->absmax, other->absmin, other->absmax ) )
1064 			{//not touching
1065 				return;
1066 			}
1067 		}
1068 		GEntity_TouchFunc( ent, other, NULL );
1069 	}
1070 	else
1071 	{//use me
1072 		if ( ent->spawnflags & 32 ) // invisible
1073 		{
1074 			// If it was invisible, first use makes it visible....
1075 			ent->s.eFlags &= ~EF_NODRAW;
1076 			ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
1077 
1078 			ent->spawnflags &= ~32;
1079 			return;
1080 		}
1081 
1082 		G_ActivateBehavior( ent, BSET_USE );
1083 		RespawnItem( ent );
1084 	}
1085 }
1086 
1087 //======================================================================
1088 
1089 /*
1090 ================
1091 FinishSpawningItem
1092 
1093 Traces down to find where an item should rest, instead of letting them
1094 free fall from their spawn points
1095 ================
1096 */
1097 extern int delayedShutDown;
1098 extern cvar_t	*g_saber;
FinishSpawningItem(gentity_t * ent)1099 void FinishSpawningItem( gentity_t *ent ) {
1100 	trace_t		tr;
1101 	vec3_t		dest;
1102 	gitem_t		*item;
1103 	int			itemNum;
1104 
1105 	itemNum=1;
1106 	for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
1107 	{
1108 		if (!strcmp(item->classname,ent->classname))
1109 		{
1110 			break;
1111 		}
1112 	}
1113 
1114 	// Set bounding box for item
1115 	VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
1116 	VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
1117 
1118 	if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
1119 		(!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
1120 	{
1121 		VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file!
1122 		VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
1123 	}
1124 
1125 	if ((item->quantity) && (item->giType == IT_AMMO))
1126 	{
1127 		ent->count = item->quantity;
1128 	}
1129 
1130 	if ((item->quantity) && (item->giType == IT_BATTERY))
1131 	{
1132 		ent->count = item->quantity;
1133 	}
1134 
1135 	ent->s.radius = 20;
1136 	VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
1137 
1138 	if ( ent->item->giType == IT_WEAPON
1139 		&& ent->item->giTag == WP_SABER
1140 		&& ent->NPC_type
1141 		&& ent->NPC_type[0] )
1142 	{
1143 		saberInfo_t itemSaber;
1144 		if ( Q_stricmp( "player", ent->NPC_type ) == 0
1145 			&& g_saber->string
1146 			&& g_saber->string[0]
1147 			&& Q_stricmp( "none", g_saber->string )
1148 			&& Q_stricmp( "NULL", g_saber->string ) )
1149 		{//player's saber
1150 			WP_SaberParseParms( g_saber->string, &itemSaber );
1151 		}
1152 		else
1153 		{//specific saber
1154 			WP_SaberParseParms( ent->NPC_type, &itemSaber );
1155 		}
1156 		//NOTE:  should I keep this string around for any reason?  Will I ever need it later?
1157 		//ent->??? = G_NewString( itemSaber.model );
1158 		gi.G2API_InitGhoul2Model( ent->ghoul2, itemSaber.model, G_ModelIndex( itemSaber.model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
1159 		WP_SaberFreeStrings(itemSaber);
1160 	}
1161 	else
1162 	{
1163 		gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
1164 	}
1165 
1166 	// Set crystal ammo amount based on skill level
1167 /*	if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
1168 		(itemNum == ITM_AMMO_CRYSTAL_DN) ||
1169 		(itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
1170 		(itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
1171 		(itemNum == ITM_AMMO_CRYSTAL_STASIS))
1172 	{
1173 		CrystalAmmoSettings(ent);
1174 	}
1175 */
1176 	ent->s.eType = ET_ITEM;
1177 	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
1178 	ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
1179 
1180 	ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
1181 	ent->e_TouchFunc = touchF_Touch_Item;
1182 	// useing an item causes it to respawn
1183 	ent->e_UseFunc = useF_Use_Item;
1184 	ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up
1185 
1186 	// Hang in air?
1187 	ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid
1188 	if ( (ent->spawnflags&ITMSF_SUSPEND)
1189 		|| (ent->flags&FL_DROPPED_ITEM) )
1190 	{
1191 		// suspended
1192 		G_SetOrigin( ent, ent->s.origin );
1193 	}
1194 	else
1195 	{
1196 		// drop to floor
1197 		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD );
1198 		gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, (EG2_Collision)0, 0 );
1199 		if ( tr.startsolid )
1200 		{
1201 			if ( &g_entities[tr.entityNum] != NULL )
1202 			{
1203 				gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin), g_entities[tr.entityNum].classname );
1204 			}
1205 			else
1206 			{
1207 				gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) );
1208 			}
1209 			assert( 0 && "item starting in solid");
1210 			if (!g_entities[ENTITYNUM_WORLD].s.radius){	//not a region
1211 				delayedShutDown = level.time + 100;
1212 			}
1213 			G_FreeEntity( ent );
1214 			return;
1215 		}
1216 
1217 		// allow to ride movers
1218 		ent->s.groundEntityNum = tr.entityNum;
1219 
1220 		G_SetOrigin( ent, tr.endpos );
1221 	}
1222 
1223 /* ? don't need this
1224 	// team slaves and targeted items aren't present at start
1225 	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
1226 		ent->s.eFlags |= EF_NODRAW;
1227 		ent->contents = 0;
1228 		return;
1229 	}
1230 */
1231 	if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
1232 	{
1233 		ent->s.eFlags |= EF_NODRAW;
1234 		ent->contents = 0;
1235 	}
1236 
1237 	if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
1238 	{
1239 		ent->contents = 0;
1240 	}
1241 
1242 	if ( (ent->spawnflags&ITMSF_STATIONARY) )
1243 	{//can't be pushed around
1244 		ent->flags |= FL_NO_KNOCKBACK;
1245 	}
1246 
1247 	if ( (ent->flags&FL_DROPPED_ITEM) )
1248 	{//go away after 30 seconds
1249 		ent->e_ThinkFunc = thinkF_G_FreeEntity;
1250 		ent->nextthink = level.time + 30000;
1251 	}
1252 
1253 	gi.linkentity (ent);
1254 }
1255 
1256 
1257 char itemRegistered[MAX_ITEMS+1];
1258 
1259 
1260 /*
1261 ==============
1262 ClearRegisteredItems
1263 ==============
1264 */
ClearRegisteredItems(void)1265 void ClearRegisteredItems( void ) {
1266 	for ( int i = 0; i < bg_numItems; i++ )
1267 	{
1268 		itemRegistered[i] = '0';
1269 	}
1270 	itemRegistered[ bg_numItems ] = 0;
1271 
1272 	//these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
1273 	//RegisterItem( FindItemForWeapon( WP_NONE ) );	//has no item
1274 	RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS ));
1275 	//RegisterItem( FindItemForInventory( INV_BACTA_CANISTER ));
1276 	// saber or baton is cached in SP_info_player_deathmatch now.
1277 
1278 extern void Player_CacheFromPrevLevel(void);//g_client.cpp
1279 	Player_CacheFromPrevLevel();	//reads from transition carry-over;
1280 }
1281 
1282 /*
1283 ===============
1284 RegisterItem
1285 
1286 The item will be added to the precache list
1287 ===============
1288 */
RegisterItem(gitem_t * item)1289 void RegisterItem( gitem_t *item ) {
1290 	if ( !item ) {
1291 		G_Error( "RegisterItem: NULL" );
1292 	}
1293 	itemRegistered[ item - bg_itemlist ] = '1';
1294 	gi.SetConfigstring(CS_ITEMS, itemRegistered);	//Write the needed items to a config string
1295 }
1296 
1297 
1298 /*
1299 ===============
1300 SaveRegisteredItems
1301 
1302 Write the needed items to a config string
1303 so the client will know which ones to precache
1304 ===============
1305 */
SaveRegisteredItems(void)1306 void SaveRegisteredItems( void ) {
1307 /*	char	string[MAX_ITEMS+1];
1308 	int		i;
1309 	int		count;
1310 
1311 	count = 0;
1312 	for ( i = 0 ; i < bg_numItems ; i++ ) {
1313 		if ( itemRegistered[i] ) {
1314 			count++;
1315 			string[i] = '1';
1316 		} else {
1317 			string[i] = '0';
1318 		}
1319 	}
1320 	string[ bg_numItems ] = 0;
1321 
1322 	gi.Printf( "%i items registered\n", count );
1323 	gi.SetConfigstring(CS_ITEMS, string);
1324 */
1325 	gi.SetConfigstring(CS_ITEMS, itemRegistered);
1326 }
1327 
1328 /*
1329 ============
1330 item_spawn_use
1331 
1332  if an item is given a targetname, it will be spawned in when used
1333 ============
1334 */
item_spawn_use(gentity_t * self,gentity_t * other,gentity_t * activator)1335 void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1336 //-----------------------------------------------------------------------------
1337 {
1338 	self->nextthink = level.time + 50;
1339 	self->e_ThinkFunc = thinkF_FinishSpawningItem;
1340 	// I could be fancy and add a count or something like that to be able to spawn the item numerous times...
1341 	self->e_UseFunc = useF_NULL;
1342 }
1343 
1344 /*
1345 ============
1346 G_SpawnItem
1347 
1348 Sets the clipping size and plants the object on the floor.
1349 
1350 Items can't be immediately dropped to floor, because they might
1351 be on an entity that hasn't spawned yet.
1352 ============
1353 */
G_SpawnItem(gentity_t * ent,gitem_t * item)1354 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
1355 	G_SpawnFloat( "random", "0", &ent->random );
1356 	G_SpawnFloat( "wait", "0", &ent->wait );
1357 
1358 	RegisterItem( item );
1359 	ent->item = item;
1360 
1361 	// targetname indicates they want to spawn it later
1362 	if( ent->targetname )
1363 	{
1364 		ent->e_UseFunc = useF_item_spawn_use;
1365 	}
1366 	else
1367 	{	// some movers spawn on the second frame, so delay item
1368 		// spawns until the third frame so they can ride trains
1369 		ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50;
1370 		ent->e_ThinkFunc = thinkF_FinishSpawningItem;
1371 	}
1372 
1373 	ent->physicsBounce = 0.50;		// items are bouncy
1374 
1375 	// Set a default infoString text color
1376 	// NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it
1377 	VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f );
1378 
1379 	if ( ent->team && ent->team[0] )
1380 	{
1381 		ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team );
1382 		if ( ent->noDamageTeam == TEAM_FREE )
1383 		{
1384 			G_Error("team name %s not recognized\n", ent->team);
1385 		}
1386 	}
1387 
1388 	if ( ent->item
1389 		&& ent->item->giType == IT_WEAPON
1390 		&& ent->item->giTag == WP_SABER )
1391 	{//weapon_saber item
1392 		if ( !ent->count )
1393 		{//can only pick up once
1394 			ent->count = 1;
1395 		}
1396 	}
1397 	ent->team = NULL;
1398 }
1399 
1400 
1401 /*
1402 ================
1403 G_BounceItem
1404 
1405 ================
1406 */
G_BounceItem(gentity_t * ent,trace_t * trace)1407 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
1408 	vec3_t	velocity;
1409 	float	dot;
1410 	int		hitTime;
1411 	qboolean droppedSaber = qtrue;
1412 
1413 	if ( ent->item
1414 		&& ent->item->giType == IT_WEAPON
1415 		&& ent->item->giTag == WP_SABER
1416 		&& (ent->flags&FL_DROPPED_ITEM) )
1417 	{
1418 		droppedSaber = qtrue;
1419 	}
1420 
1421 	// reflect the velocity on the trace plane
1422 	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
1423 	EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
1424 	dot = DotProduct( velocity, trace->plane.normal );
1425 	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
1426 
1427 	// cut the velocity to keep from bouncing forever
1428 	VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
1429 
1430 	if ( droppedSaber )
1431 	{//a dropped saber item
1432 		//FIXME: use NPC_type (as saberType) to get proper bounce sound?
1433 		WP_SaberFallSound( NULL, ent );
1434 	}
1435 
1436 	// check for stop
1437 	if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 )
1438 	{//stop
1439 		G_SetOrigin( ent, trace->endpos );
1440 		ent->s.groundEntityNum = trace->entityNum;
1441 		if ( droppedSaber )
1442 		{//a dropped saber item
1443 			//stop rotation
1444 			VectorClear( ent->s.apos.trDelta );
1445 			ent->currentAngles[PITCH] = SABER_PITCH_HACK;
1446 			ent->currentAngles[ROLL] = 0;
1447 			if ( ent->NPC_type
1448 				&& ent->NPC_type[0] )
1449 			{//we have a valid saber for this
1450 				saberInfo_t saber;
1451 				if ( WP_SaberParseParms( ent->NPC_type, &saber ) )
1452 				{
1453 					if ( (saber.saberFlags&SFL_BOLT_TO_WRIST) )
1454 					{
1455 						ent->currentAngles[PITCH] = 0;
1456 					}
1457 				}
1458 			}
1459 			pitch_roll_for_slope( ent, trace->plane.normal, ent->currentAngles, qtrue );
1460 			G_SetAngles( ent, ent->currentAngles );
1461 		}
1462 		return;
1463 	}
1464 	//bounce
1465 	if ( droppedSaber )
1466 	{//a dropped saber item
1467 		//change rotation
1468 		VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1469 		ent->s.apos.trType = TR_LINEAR;
1470 		ent->s.apos.trTime = level.time;
1471 		VectorSet( ent->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
1472 	}
1473 
1474 	VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
1475 	VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1476 	ent->s.pos.trTime = level.time;
1477 }
1478 
1479 
1480 /*
1481 ================
1482 G_RunItem
1483 
1484 ================
1485 */
G_RunItem(gentity_t * ent)1486 void G_RunItem( gentity_t *ent ) {
1487 	vec3_t		origin;
1488 	trace_t		tr;
1489 	int			contents;
1490 	int			mask;
1491 
1492 	// if groundentity has been set to -1, it may have been pushed off an edge
1493 	if ( ent->s.groundEntityNum == ENTITYNUM_NONE )
1494 	{
1495 		if ( ent->s.pos.trType != TR_GRAVITY )
1496 		{
1497 			ent->s.pos.trType = TR_GRAVITY;
1498 			ent->s.pos.trTime = level.time;
1499 		}
1500 	}
1501 
1502 	if ( ent->s.pos.trType == TR_STATIONARY )
1503 	{
1504 		// check think function
1505 		G_RunThink( ent );
1506 		if ( !g_gravity->value )
1507 		{
1508 			ent->s.pos.trType = TR_GRAVITY;
1509 			ent->s.pos.trTime = level.time;
1510 			ent->s.pos.trDelta[0] += Q_flrand(-1.0f, 1.0f) * 40.0f; // I dunno, just do this??
1511 			ent->s.pos.trDelta[1] += Q_flrand(-1.0f, 1.0f) * 40.0f;
1512 			ent->s.pos.trDelta[2] += Q_flrand(0.0f, 1.0f) * 20.0f;
1513 		}
1514 		else if ( (ent->flags&FL_DROPPED_ITEM)
1515 			&& ent->item
1516 			&& ent->item->giType == IT_WEAPON
1517 			&& ent->item->giTag == WP_SABER )
1518 		{//a dropped saber item, check below, just in case
1519 			int ignore = ENTITYNUM_NONE;
1520 			if ( ent->clipmask )
1521 			{
1522 				mask = ent->clipmask;
1523 			}
1524 			else
1525 			{
1526 				mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1527 			}
1528 			if ( ent->owner )
1529 			{
1530 				ignore = ent->owner->s.number;
1531 			}
1532 			else if ( ent->activator )
1533 			{
1534 				ignore = ent->activator->s.number;
1535 			}
1536 			VectorSet( origin, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2]-1 );
1537 			gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
1538 			if ( !tr.allsolid
1539 				&& !tr.startsolid
1540 				&& tr.fraction > 0.001f )
1541 			{//wha?  fall....
1542 				ent->s.pos.trType = TR_GRAVITY;
1543 				ent->s.pos.trTime = level.time;
1544 			}
1545 		}
1546 		return;
1547 	}
1548 
1549 	// get current position
1550 	EvaluateTrajectory( &ent->s.pos, level.time, origin );
1551 	if ( ent->s.apos.trType != TR_STATIONARY )
1552 	{
1553 		EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1554 		G_SetAngles( ent, ent->currentAngles );
1555 	}
1556 
1557 	// trace a line from the previous position to the current position
1558 	if ( ent->clipmask )
1559 	{
1560 		mask = ent->clipmask;
1561 	}
1562 	else
1563 	{
1564 		mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1565 	}
1566 
1567 	int ignore = ENTITYNUM_NONE;
1568 	if ( ent->owner )
1569 	{
1570 		ignore = ent->owner->s.number;
1571 	}
1572 	else if ( ent->activator )
1573 	{
1574 		ignore = ent->activator->s.number;
1575 	}
1576 	gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
1577 
1578 	VectorCopy( tr.endpos, ent->currentOrigin );
1579 
1580 	if ( tr.startsolid )
1581 	{
1582 		tr.fraction = 0;
1583 	}
1584 
1585 	gi.linkentity( ent );	// FIXME: avoid this for stationary?
1586 
1587 	// check think function
1588 	G_RunThink( ent );
1589 
1590 	if ( tr.fraction == 1 )
1591 	{
1592 		if ( g_gravity->value <= 0 )
1593 		{
1594 			if ( ent->s.apos.trType != TR_LINEAR )
1595 			{
1596 				VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1597 				ent->s.apos.trType = TR_LINEAR;
1598 				ent->s.apos.trDelta[1] = Q_flrand( -300, 300 );
1599 				ent->s.apos.trDelta[0] = Q_flrand( -10, 10 );
1600 				ent->s.apos.trDelta[2] = Q_flrand( -10, 10 );
1601 				ent->s.apos.trTime = level.time;
1602 			}
1603 		}
1604 		//friction in zero-G
1605 		if ( !g_gravity->value )
1606 		{
1607 			float friction = 0.975f;
1608 			/*friction -= ent->mass/1000.0f;
1609 			if ( friction < 0.1 )
1610 			{
1611 				friction = 0.1f;
1612 			}
1613 			*/
1614 			VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta );
1615 			VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1616 			ent->s.pos.trTime = level.time;
1617 		}
1618 		return;
1619 	}
1620 
1621 	// if it is in a nodrop volume, remove it
1622 	contents = gi.pointcontents( ent->currentOrigin, -1 );
1623 	if ( contents & CONTENTS_NODROP )
1624 	{
1625 		G_FreeEntity( ent );
1626 		return;
1627 	}
1628 
1629 	if ( !tr.startsolid )
1630 	{
1631 		G_BounceItem( ent, &tr );
1632 	}
1633 }
1634 
1635 /*
1636 ================
1637 ItemUse_Bacta
1638 
1639 ================
1640 */
ItemUse_Bacta(gentity_t * ent)1641 void ItemUse_Bacta(gentity_t *ent)
1642 {
1643 	if (!ent || !ent->client)
1644 	{
1645 		return;
1646 	}
1647 
1648 	if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] )
1649 	{
1650 		return;
1651 	}
1652 
1653 	ent->health += MAX_BACTA_HEAL_AMOUNT;
1654 
1655 	if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1656 	{
1657 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1658 	}
1659 
1660 	ent->client->ps.inventory[INV_BACTA_CANISTER]--;
1661 
1662 	G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", Q_irand( 1, 4 ), g_sex->string[0] ) );
1663 }
1664 
1665