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 // leave this line at the top for all g_xxxx.cpp files...
25 #include "g_headers.h"
26 
27 #include "g_local.h"
28 #include "g_functions.h"
29 #include "g_items.h"
30 #include "wp_saber.h"
31 
32 extern qboolean	missionInfo_Updated;
33 
34 extern void CrystalAmmoSettings(gentity_t *ent);
35 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel );
36 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
37 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
38 extern qboolean PM_InKnockDown( playerState_t *ps );
39 extern qboolean PM_InGetUp( playerState_t *ps );
40 
41 extern	cvar_t	*g_spskill;
42 
43 #define MAX_BACTA_HEAL_AMOUNT		25
44 
45 /*
46 
47   Items are any object that a player can touch to gain some effect.
48 
49   Pickup will return the number of seconds until they should respawn.
50 
51   all items should pop when dropped in lava or slime
52 
53   Respawnable items don't actually go away when picked up, they are
54   just made invisible and untouchable.  This allows them to ride
55   movers and respawn apropriately.
56 */
57 
58 // Item Spawn flags
59 #define ITMSF_SUSPEND		1
60 #define ITMSF_TEAM			2
61 #define ITMSF_MONSTER		4
62 #define ITMSF_NOTSOLID		8
63 #define ITMSF_VERTICAL		16
64 #define ITMSF_INVISIBLE		32
65 
66 //======================================================================
67 
68 /*
69 ===============
70 G_InventorySelectable
71 ===============
72 */
G_InventorySelectable(int index,gentity_t * other)73 qboolean G_InventorySelectable( int index,gentity_t *other)
74 {
75 	if (other->client->ps.inventory[index])
76 	{
77 		return qtrue;
78 	}
79 
80 	return qfalse;
81 }
82 
83 extern qboolean INV_GoodieKeyGive( gentity_t *target );
84 extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
Pickup_Holdable(gentity_t * ent,gentity_t * other)85 int Pickup_Holdable( gentity_t *ent, gentity_t *other )
86 {
87 	int		i,original;
88 
89 	other->client->ps.stats[STAT_ITEMS] |= (1<<ent->item->giTag);
90 
91 	if ( ent->item->giTag == INV_SECURITY_KEY )
92 	{//give the key
93 		//FIXME: temp message
94 		gi.SendServerCommand( 0, "cp @INGAME_YOU_TOOK_SECURITY_KEY" );
95 		INV_SecurityKeyGive( other, ent->message );
96 	}
97 	else if ( ent->item->giTag == INV_GOODIE_KEY )
98 	{//give the key
99 		//FIXME: temp message
100 		gi.SendServerCommand( 0, "cp @INGAME_YOU_TOOK_SUPPLY_KEY" );
101 		INV_GoodieKeyGive( other );
102 	}
103 	else
104 	{// Picking up a normal item?
105 		other->client->ps.inventory[ent->item->giTag]++;
106 	}
107 	// Got a security key
108 
109 	// Set the inventory select, just in case it hasn't
110 	original = cg.inventorySelect;
111 	for ( i = 0 ; i < INV_MAX ; i++ )
112 	{
113 		if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
114 		{
115 			cg.inventorySelect = (INV_MAX - 1);
116 		}
117 
118 		if ( G_InventorySelectable( cg.inventorySelect,other ) )
119 		{
120 			return 60;
121 		}
122 		cg.inventorySelect++;
123 	}
124 
125 	cg.inventorySelect = original;
126 
127 	return 60;
128 }
129 
130 
131 //======================================================================
Add_Ammo2(gentity_t * ent,int ammoType,int count)132 int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
133 {
134 
135 	if (ammoType != AMMO_FORCE)
136 	{
137 		ent->client->ps.ammo[ammoType] += count;
138 
139 		// since the ammo is the weapon in this case, picking up ammo should actually give you the weapon
140 		switch( ammoType )
141 		{
142 		case AMMO_THERMAL:
143 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL );
144 			break;
145 		case AMMO_DETPACK:
146 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK );
147 			break;
148 		case AMMO_TRIPMINE:
149 			ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE );
150 			break;
151 		}
152 
153 		if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
154 		{
155 			ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
156 			return qfalse;
157 		}
158 	}
159 	else
160 	{
161 		if ( ent->client->ps.forcePower >= ammoData[ammoType].max )
162 		{//if have full force, just get 25 extra per crystal
163 			ent->client->ps.forcePower += 25;
164 		}
165 		else
166 		{//else if don't have full charge, give full amount, up to max + 25
167 			ent->client->ps.forcePower += count;
168 			if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 )
169 			{//cap at max + 25
170 				ent->client->ps.forcePower = ammoData[ammoType].max + 25;
171 			}
172 		}
173 
174 		if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 )
175 		{//always cap at twice a full charge
176 			ent->client->ps.forcePower = ammoData[ammoType].max*2;
177 			return qfalse;		// can't hold any more
178 		}
179 	}
180 	return qtrue;
181 }
182 
183 //-------------------------------------------------------
Add_Ammo(gentity_t * ent,int weapon,int count)184 void Add_Ammo (gentity_t *ent, int weapon, int count)
185 {
186 	Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
187 }
188 
189 //-------------------------------------------------------
Pickup_Ammo(gentity_t * ent,gentity_t * other)190 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
191 {
192 	int		quantity;
193 
194 	if ( ent->count ) {
195 		quantity = ent->count;
196 	} else {
197 		quantity = ent->item->quantity;
198 	}
199 
200 	Add_Ammo2 (other, ent->item->giTag, quantity);
201 
202 	return 30;
203 }
204 
205 //======================================================================
Add_Batteries(gentity_t * ent,int * count)206 void Add_Batteries( gentity_t *ent, int *count )
207 {
208 	if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count )
209 	{
210 		if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES )
211 		{
212 			// steal what we need, then leave the rest for later
213 			*count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge );
214 			ent->client->ps.batteryCharge = MAX_BATTERIES;
215 		}
216 		else
217 		{
218 			// just drain all of the batteries
219 			ent->client->ps.batteryCharge += *count;
220 			*count = 0;
221 		}
222 
223 		G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 );
224 	}
225 }
226 
227 //-------------------------------------------------------
Pickup_Battery(gentity_t * ent,gentity_t * other)228 int Pickup_Battery( gentity_t *ent, gentity_t *other )
229 {
230 	int	quantity;
231 
232 	if ( ent->count )
233 	{
234 		quantity = ent->count;
235 	}
236 	else
237 	{
238 		quantity = ent->item->quantity;
239 	}
240 
241 	// 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
242 	Add_Batteries( other, &quantity );
243 
244 	return 30;
245 }
246 
247 //======================================================================
248 
249 
250 extern void WP_SaberInitBladeData( gentity_t *ent );
251 extern void CG_ChangeWeapon( int num );
Pickup_Weapon(gentity_t * ent,gentity_t * other)252 int Pickup_Weapon (gentity_t *ent, gentity_t *other)
253 {
254 	int		quantity;
255 	qboolean	hadWeapon = qfalse;
256 
257 	/*
258 	if ( ent->count || (ent->activator && !ent->activator->s.number) )
259 	{
260 		quantity = ent->count;
261 	}
262 	else
263 	{
264 		quantity = ent->item->quantity;
265 	}
266 	*/
267 
268 	// dropped items are always picked up
269 	if ( ent->flags & FL_DROPPED_ITEM )
270 	{
271 		quantity = ent->count;
272 	}
273 	else
274 	{//wasn't dropped
275 		quantity = ent->item->quantity?ent->item->quantity:50;
276 	}
277 
278 	// add the weapon
279 	if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
280 	{
281 		hadWeapon = qtrue;
282 	}
283 	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
284 
285 	if ( ent->item->giTag == WP_SABER && !hadWeapon )
286 	{
287 		WP_SaberInitBladeData( other );
288 	}
289 
290 	if ( other->s.number )
291 	{//NPC
292 		if ( other->s.weapon == WP_NONE )
293 		{//NPC with no weapon picked up a weapon, change to this weapon
294 			//FIXME: clear/set the alt-fire flag based on the picked up weapon and my class?
295 			other->client->ps.weapon = ent->item->giTag;
296 			other->client->ps.weaponstate = WEAPON_RAISING;
297 			ChangeWeapon( other, ent->item->giTag );
298 			if ( ent->item->giTag == WP_SABER )
299 			{
300 				other->client->ps.saberActive = qtrue;
301 				G_CreateG2AttachedWeaponModel( other, other->client->ps.saberModel );
302 			}
303 			else
304 			{
305 				G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl );
306 			}
307 		}
308 	}
309 
310 	if ( quantity )
311 	{
312 		// Give ammo
313 		Add_Ammo( other, ent->item->giTag, quantity );
314 	}
315 	return 5;
316 }
317 
318 
319 //======================================================================
320 
ITM_AddHealth(gentity_t * ent,int count)321 int ITM_AddHealth (gentity_t *ent, int count)
322 {
323 
324 	ent->health += count;
325 
326 	if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])	// Past max health
327 	{
328 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
329 
330 		return qfalse;
331 	}
332 
333 	return qtrue;
334 
335 }
336 
Pickup_Health(gentity_t * ent,gentity_t * other)337 int Pickup_Health (gentity_t *ent, gentity_t *other) {
338 	int			max;
339 	int			quantity;
340 
341 	max = other->client->ps.stats[STAT_MAX_HEALTH];
342 
343 	if ( ent->count ) {
344 		quantity = ent->count;
345 	} else {
346 		quantity = ent->item->quantity;
347 	}
348 
349 	other->health += quantity;
350 
351 	if (other->health > max ) {
352 		other->health = max;
353 	}
354 
355 	if ( ent->item->giTag == 100 ) {		// mega health respawns slow
356 		return 120;
357 	}
358 
359 	return 30;
360 }
361 
362 //======================================================================
363 
ITM_AddArmor(gentity_t * ent,int count)364 int ITM_AddArmor (gentity_t *ent, int count)
365 {
366 
367 	ent->client->ps.stats[STAT_ARMOR] += count;
368 
369 	if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
370 	{
371 		ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
372 		return qfalse;
373 	}
374 
375 	return qtrue;
376 }
377 
378 
Pickup_Armor(gentity_t * ent,gentity_t * other)379 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
380 
381 	// make sure that the shield effect is on
382 	other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
383 
384 	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
385 	if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) {
386 		other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
387 	}
388 
389 	return 30;
390 }
391 
392 
393 
394 //======================================================================
395 
Pickup_Holocron(gentity_t * ent,gentity_t * other)396 int Pickup_Holocron( gentity_t *ent, gentity_t *other )
397 {
398 	int forcePower = ent->item->giTag;
399 	int forceLevel = ent->count;
400 	// check if out of range
401 	if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS )
402 	{
403 		gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel );
404 		return 1;
405 	}
406 
407 	// don't pick up if already known AND your level is higher than pickup level
408 	if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) )
409 	{
410 		//don't pickup if item is lower than current level
411 		if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel )
412 		{
413 			return 1;
414 		}
415 	}
416 
417 	other->client->ps.forcePowerLevel[forcePower] = forceLevel;
418 	other->client->ps.forcePowersKnown |= ( 1 << forcePower );
419 
420 	missionInfo_Updated = qtrue;	// Activate flashing text
421 	gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine.
422 	cg_updatedDataPadForcePower1.integer = forcePower+1;
423 	gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine.
424 	cg_updatedDataPadForcePower2.integer = 0;
425 	gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine.
426 	cg_updatedDataPadForcePower3.integer = 0;
427 
428 	return 1;
429 }
430 
431 
432 //======================================================================
433 
434 /*
435 ===============
436 RespawnItem
437 ===============
438 */
RespawnItem(gentity_t * ent)439 void RespawnItem( gentity_t *ent ) {
440 }
441 
442 
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)443 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
444 {
445 	if ( !item->item ) {
446 		return qfalse;
447 	}
448 	if ( item->item->giType == IT_HOLDABLE &&
449 		item->item->giTag == INV_SECURITY_KEY ) {
450 		return qfalse;
451 	}
452 	if ( (item->flags&FL_DROPPED_ITEM)
453 		&& item->activator != &g_entities[0]
454 		&& pickerupper->s.number
455 		&& pickerupper->s.weapon == WP_NONE
456 		&& pickerupper->enemy
457 		&& pickerupper->painDebounceTime < level.time
458 		&& pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
459 		&& !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) ) // not being forced to march
460 	{//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
461 		if ( level.time - item->s.time < 3000 )//was 5000
462 		{
463 			return qfalse;
464 		}
465 		return qtrue;
466 	}
467 	return qfalse;
468 }
469 /*
470 ===============
471 Touch_Item
472 ===============
473 */
474 extern cvar_t		*g_timescale;
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)475 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
476 	int			respawn = 0;
477 
478 	if (!other->client)
479 		return;
480 	if (other->health < 1)
481 		return;		// dead people can't pickup
482 
483 	if ( other->client->ps.pm_time > 0 )
484 	{//cant pick up when out of control
485 		return;
486 	}
487 
488 	// Only monsters can pick it up
489 	if ((ent->spawnflags &  ITMSF_MONSTER) && (other->client->playerTeam == TEAM_PLAYER))
490 	{
491 		return;
492 	}
493 
494 	// Only starfleet can pick it up
495 	if ((ent->spawnflags &  ITMSF_TEAM) && (other->client->playerTeam != TEAM_PLAYER))
496 	{
497 		return;
498 	}
499 
500 
501 	if ( other->client->NPC_class == CLASS_ATST ||
502 		other->client->NPC_class == CLASS_GONK ||
503 		other->client->NPC_class == CLASS_MARK1 ||
504 		other->client->NPC_class == CLASS_MARK2 ||
505 		other->client->NPC_class == CLASS_MOUSE ||
506 		other->client->NPC_class == CLASS_PROBE ||
507 		other->client->NPC_class == CLASS_PROTOCOL ||
508 		other->client->NPC_class == CLASS_R2D2 ||
509 		other->client->NPC_class == CLASS_R5D2 ||
510 		other->client->NPC_class == CLASS_SEEKER ||
511 		other->client->NPC_class == CLASS_REMOTE ||
512 		other->client->NPC_class == CLASS_SENTRY )
513 	{//FIXME: some flag would be better
514 		//droids can't pick up items/weapons!
515 		return;
516 	}
517 
518 	//FIXME: need to make them run toward a dropped weapon when fleeing without one?
519 	//FIXME: need to make them come out of flee mode when pick up their old weapon?
520 	if ( CheckItemCanBePickedUpByNPC( ent, other ) )
521 	{
522 		if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent )
523 		{//they were running to pick me up, they did, so clear goal
524 			other->NPC->goalEntity = NULL;
525 			other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
526 		}
527 	}
528 	else if (!(ent->spawnflags &  ITMSF_TEAM) && !(ent->spawnflags &  ITMSF_MONSTER))
529 	{// Only player can pick it up
530 		if ( other->s.number != 0 )	// Not the player?
531 		{
532 			return;
533 		}
534 	}
535 
536 	// the same pickup rules are used for client side and server side
537 	if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
538 		return;
539 	}
540 
541 	if ( other->client )
542 	{
543 		if ( other->client->ps.eFlags&EF_FORCE_GRIPPED )
544 		{//can't pick up anything while being gripped
545 			return;
546 		}
547 		if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) )
548 		{//can't pick up while in a knockdown
549 			return;
550 		}
551 	}
552 	if (!ent->item) {		//not an item!
553 		gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
554 		return;
555 	}
556 	qboolean bHadWeapon = qfalse;
557 	// call the item-specific pickup function
558 	switch( ent->item->giType )
559 	{
560 	case IT_WEAPON:
561 		if ( other->NPC && other->s.weapon == WP_NONE )
562 		{//Make them duck and sit here for a few seconds
563 			int pickUpTime = Q_irand( 1000, 3000 );
564 			TIMER_Set( other, "duck", pickUpTime );
565 			TIMER_Set( other, "roamTime", pickUpTime );
566 			TIMER_Set( other, "stick", pickUpTime );
567 			TIMER_Set( other, "verifyCP", pickUpTime );
568 			TIMER_Set( other, "attackDelay", 600 );
569 			respawn = 0;
570 		}
571 		if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
572 		{
573 			bHadWeapon = qtrue;
574 		}
575 		respawn = Pickup_Weapon(ent, other);
576 		break;
577 	case IT_AMMO:
578 		respawn = Pickup_Ammo(ent, other);
579 		break;
580 	case IT_ARMOR:
581 		respawn = Pickup_Armor(ent, other);
582 		break;
583 	case IT_HEALTH:
584 		respawn = Pickup_Health(ent, other);
585 		break;
586 	case IT_HOLDABLE:
587 		respawn = Pickup_Holdable(ent, other);
588 		break;
589 	case IT_BATTERY:
590 		respawn = Pickup_Battery( ent, other );
591 		break;
592 	case IT_HOLOCRON:
593 		respawn = Pickup_Holocron( ent, other );
594 		break;
595 	default:
596 		return;
597 	}
598 
599 	if ( !respawn )
600 	{
601 		return;
602 	}
603 
604 	// play the normal pickup sound
605 	if ( !other->s.number && g_timescale->value < 1.0f  )
606 	{//SIGH... with timescale on, you lose events left and right
607 extern void CG_ItemPickup( int itemNum, qboolean bHadItem );
608 		// but we're SP so we'll cheat
609 		cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO,	cgi_S_RegisterSound( ent->item->pickup_sound ) );
610 		// show icon and name on status bar
611 		CG_ItemPickup( ent->s.modelindex, bHadWeapon );
612 	}
613 	else
614 	{
615 		if ( bHadWeapon )
616 		{
617 			G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex );
618 		}
619 		else
620 		{
621 			G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
622 		}
623 	}
624 
625 	// fire item targets
626 	G_UseTargets (ent, other);
627 
628 	// wait of -1 will not respawn
629 //	if ( ent->wait == -1 )
630 	{
631 		//why not just remove me?
632 		G_FreeEntity( ent );
633 		/*
634 		//NOTE: used to do this:  (for respawning?)
635 		ent->svFlags |= SVF_NOCLIENT;
636 		ent->s.eFlags |= EF_NODRAW;
637 		ent->contents = 0;
638 		ent->unlinkAfterEvent = qtrue;
639 		*/
640 		return;
641 	}
642 }
643 
644 
645 //======================================================================
646 
647 /*
648 ================
649 LaunchItem
650 
651 Spawns an item and tosses it forward
652 ================
653 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity,char * target)654 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, char *target ) {
655 	gentity_t	*dropped;
656 
657 	dropped = G_Spawn();
658 
659 	dropped->s.eType = ET_ITEM;
660 	dropped->s.modelindex = item - bg_itemlist;	// store item number in modelindex
661 	dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
662 
663 	dropped->classname = item->classname;
664 	dropped->item = item;
665 
666 	// try using the "correct" mins/maxs first
667 	VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] );
668 	VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] );
669 
670 	if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) &&
671 		(!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2]))
672 	{
673 		VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
674 		VectorScale( dropped->maxs, -1, dropped->mins );
675 	}
676 
677 	dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
678 
679 	if ( target && target[0] )
680 	{
681 		dropped->target = G_NewString( target );
682 	}
683 	else
684 	{
685 		// if not targeting something, auto-remove after 30 seconds
686 		// only if it's NOT a security or goodie key
687 		if (dropped->item->giTag != INV_SECURITY_KEY )
688 		{
689 			dropped->e_ThinkFunc = thinkF_G_FreeEntity;
690 			dropped->nextthink = level.time + 30000;
691 		}
692 
693 		if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE )
694 		{
695 			dropped->nextthink = -1;
696 			dropped->e_ThinkFunc = thinkF_NULL;
697 		}
698 	}
699 
700 	dropped->e_TouchFunc = touchF_Touch_Item;
701 
702 	if ( item->giType == IT_WEAPON )
703 	{
704 		// give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster
705 		if ( item->giTag != WP_BOWCASTER
706 			&& item->giTag != WP_THERMAL
707 			&& item->giTag != WP_TRIP_MINE
708 			&& item->giTag != WP_DET_PACK )
709 		{
710 			VectorSet( dropped->s.angles, 0, Q_flrand(-1.0f, 1.0f) * 180, 90.0f );
711 			G_SetAngles( dropped, dropped->s.angles );
712 		}
713 	}
714 
715 	G_SetOrigin( dropped, origin );
716 	dropped->s.pos.trType = TR_GRAVITY;
717 	dropped->s.pos.trTime = level.time;
718 	VectorCopy( velocity, dropped->s.pos.trDelta );
719 
720 	dropped->s.eFlags |= EF_BOUNCE_HALF;
721 
722 	dropped->flags = FL_DROPPED_ITEM;
723 
724 	gi.linkentity (dropped);
725 
726 	return dropped;
727 }
728 
729 /*
730 ================
731 Drop_Item
732 
733 Spawns an item and tosses it forward
734 ================
735 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle,qboolean copytarget)736 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) {
737 	gentity_t	*dropped = NULL;
738 	vec3_t	velocity;
739 	vec3_t	angles;
740 
741 	VectorCopy( ent->s.apos.trBase, angles );
742 	angles[YAW] += angle;
743 	angles[PITCH] = 0;	// always forward
744 
745 	AngleVectors( angles, velocity, NULL, NULL );
746 	VectorScale( velocity, 150, velocity );
747 	velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
748 
749 	if ( copytarget )
750 	{
751 		dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
752 	}
753 	else
754 	{
755 		dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
756 	}
757 
758 	dropped->activator = ent;//so we know who we belonged to so they can pick it back up later
759 	dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us
760 	return dropped;
761 }
762 
763 
764 /*
765 ================
766 Use_Item
767 
768 Respawn the item
769 ================
770 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)771 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
772 {
773 	if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number )
774 	{//used directly by the player, pick me up
775 		GEntity_TouchFunc( ent, other, NULL );
776 	}
777 	else
778 	{//use me
779 		if ( ent->spawnflags & 32 ) // invisible
780 		{
781 			// If it was invisible, first use makes it visible....
782 			ent->s.eFlags &= ~EF_NODRAW;
783 			ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
784 
785 			ent->spawnflags &= ~32;
786 			return;
787 		}
788 
789 		G_ActivateBehavior( ent, BSET_USE );
790 		RespawnItem( ent );
791 	}
792 }
793 
794 //======================================================================
795 
796 /*
797 ================
798 FinishSpawningItem
799 
800 Traces down to find where an item should rest, instead of letting them
801 free fall from their spawn points
802 ================
803 */
804 #ifndef FINAL_BUILD
805 extern int delayedShutDown;
806 #endif
FinishSpawningItem(gentity_t * ent)807 void FinishSpawningItem( gentity_t *ent ) {
808 	trace_t		tr;
809 	vec3_t		dest;
810 	gitem_t		*item;
811 	int			itemNum;
812 
813 	itemNum=1;
814 	for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
815 	{
816 		if (!strcmp(item->classname,ent->classname))
817 		{
818 			break;
819 		}
820 	}
821 
822 	// Set bounding box for item
823 	VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
824 	VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
825 
826 	if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
827 		(!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
828 	{
829 		VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file!
830 		VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
831 	}
832 
833 	if ((item->quantity) && (item->giType == IT_AMMO))
834 	{
835 		ent->count = item->quantity;
836 	}
837 
838 	if ((item->quantity) && (item->giType == IT_BATTERY))
839 	{
840 		ent->count = item->quantity;
841 	}
842 
843 
844 //	if ( item->giType == IT_WEAPON ) // NOTE: james thought it was ok to just always do this?
845 	{
846 		ent->s.radius = 20;
847 		VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
848 		gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
849 	}
850 
851 	// Set crystal ammo amount based on skill level
852 /*	if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
853 		(itemNum == ITM_AMMO_CRYSTAL_DN) ||
854 		(itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
855 		(itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
856 		(itemNum == ITM_AMMO_CRYSTAL_STASIS))
857 	{
858 		CrystalAmmoSettings(ent);
859 	}
860 */
861 	ent->s.eType = ET_ITEM;
862 	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
863 	ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
864 
865 	ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
866 	ent->e_TouchFunc = touchF_Touch_Item;
867 	// useing an item causes it to respawn
868 	ent->e_UseFunc = useF_Use_Item;
869 	ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up
870 
871 	// Hang in air?
872 	ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid
873 	if ( ent->spawnflags & ITMSF_SUSPEND)
874 	{
875 		// suspended
876 		G_SetOrigin( ent, ent->s.origin );
877 	}
878 	else
879 	{
880 		// drop to floor
881 		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD );
882 		gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, G2_NOCOLLIDE, 0 );
883 		if ( tr.startsolid )
884 		{
885 			if ( &g_entities[tr.entityNum] != NULL )
886 			{
887 				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 );
888 			}
889 			else
890 			{
891 				gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) );
892 			}
893 			assert( 0 && "item starting in solid");
894 #ifndef FINAL_BUILD
895 			if (!g_entities[ENTITYNUM_WORLD].s.radius){	//not a region
896 				delayedShutDown = level.time + 100;
897 			}
898 #endif
899 			G_FreeEntity( ent );
900 			return;
901 		}
902 
903 		// allow to ride movers
904 		ent->s.groundEntityNum = tr.entityNum;
905 
906 		G_SetOrigin( ent, tr.endpos );
907 	}
908 
909 /* ? don't need this
910 	// team slaves and targeted items aren't present at start
911 	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
912 		ent->s.eFlags |= EF_NODRAW;
913 		ent->contents = 0;
914 		return;
915 	}
916 */
917 	if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
918 	{
919 		ent->s.eFlags |= EF_NODRAW;
920 		ent->contents = 0;
921 	}
922 
923 	if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
924 	{
925 		ent->contents = 0;
926 	}
927 
928 	gi.linkentity (ent);
929 }
930 
931 
932 char itemRegistered[MAX_ITEMS+1];
933 
934 
935 /*
936 ==============
937 ClearRegisteredItems
938 ==============
939 */
ClearRegisteredItems(void)940 void ClearRegisteredItems( void ) {
941 	for ( int i = 0; i < bg_numItems; i++ )
942 	{
943 		itemRegistered[i] = '0';
944 	}
945 	itemRegistered[ bg_numItems ] = 0;
946 
947 	RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ) );	//these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
948 	RegisterItem( FindItemForWeapon( WP_STUN_BATON ) );			//these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
949 	RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS ));
950 	// saber or baton is cached in SP_info_player_deathmatch now.
951 
952 extern void Player_CacheFromPrevLevel(void);//g_client.cpp
953 	Player_CacheFromPrevLevel();	//reads from transition carry-over;
954 }
955 
956 /*
957 ===============
958 RegisterItem
959 
960 The item will be added to the precache list
961 ===============
962 */
RegisterItem(gitem_t * item)963 void RegisterItem( gitem_t *item ) {
964 	if ( !item ) {
965 		G_Error( "RegisterItem: NULL" );
966 	}
967 	itemRegistered[ item - bg_itemlist ] = '1';
968 	gi.SetConfigstring(CS_ITEMS, itemRegistered);	//Write the needed items to a config string
969 }
970 
971 
972 /*
973 ===============
974 SaveRegisteredItems
975 
976 Write the needed items to a config string
977 so the client will know which ones to precache
978 ===============
979 */
SaveRegisteredItems(void)980 void SaveRegisteredItems( void ) {
981 /*	char	string[MAX_ITEMS+1];
982 	int		i;
983 	int		count;
984 
985 	count = 0;
986 	for ( i = 0 ; i < bg_numItems ; i++ ) {
987 		if ( itemRegistered[i] ) {
988 			count++;
989 			string[i] = '1';
990 		} else {
991 			string[i] = '0';
992 		}
993 	}
994 	string[ bg_numItems ] = 0;
995 
996 	gi.Printf( "%i items registered\n", count );
997 	gi.SetConfigstring(CS_ITEMS, string);
998 */
999 	gi.SetConfigstring(CS_ITEMS, itemRegistered);
1000 }
1001 
1002 /*
1003 ============
1004 item_spawn_use
1005 
1006  if an item is given a targetname, it will be spawned in when used
1007 ============
1008 */
item_spawn_use(gentity_t * self,gentity_t * other,gentity_t * activator)1009 void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1010 //-----------------------------------------------------------------------------
1011 {
1012 	self->nextthink = level.time + 50;
1013 	self->e_ThinkFunc = thinkF_FinishSpawningItem;
1014 	// I could be fancy and add a count or something like that to be able to spawn the item numerous times...
1015 	self->e_UseFunc = useF_NULL;
1016 }
1017 
1018 /*
1019 ============
1020 G_SpawnItem
1021 
1022 Sets the clipping size and plants the object on the floor.
1023 
1024 Items can't be immediately dropped to floor, because they might
1025 be on an entity that hasn't spawned yet.
1026 ============
1027 */
G_SpawnItem(gentity_t * ent,gitem_t * item)1028 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
1029 	G_SpawnFloat( "random", "0", &ent->random );
1030 	G_SpawnFloat( "wait", "0", &ent->wait );
1031 
1032 	RegisterItem( item );
1033 	ent->item = item;
1034 
1035 	// targetname indicates they want to spawn it later
1036 	if( ent->targetname )
1037 	{
1038 		ent->e_UseFunc = useF_item_spawn_use;
1039 	}
1040 	else
1041 	{	// some movers spawn on the second frame, so delay item
1042 		// spawns until the third frame so they can ride trains
1043 		ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50;
1044 		ent->e_ThinkFunc = thinkF_FinishSpawningItem;
1045 	}
1046 
1047 	ent->physicsBounce = 0.50;		// items are bouncy
1048 
1049 	// Set a default infoString text color
1050 	// NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it
1051 	VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f );
1052 }
1053 
1054 
1055 /*
1056 ================
1057 G_BounceItem
1058 
1059 ================
1060 */
G_BounceItem(gentity_t * ent,trace_t * trace)1061 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
1062 	vec3_t	velocity;
1063 	float	dot;
1064 	int		hitTime;
1065 
1066 	// reflect the velocity on the trace plane
1067 	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
1068 	EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
1069 	dot = DotProduct( velocity, trace->plane.normal );
1070 	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
1071 
1072 	// cut the velocity to keep from bouncing forever
1073 	VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
1074 
1075 	// check for stop
1076 	if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
1077 		G_SetOrigin( ent, trace->endpos );
1078 		ent->s.groundEntityNum = trace->entityNum;
1079 		return;
1080 	}
1081 
1082 	VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
1083 	VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1084 	ent->s.pos.trTime = level.time;
1085 }
1086 
1087 
1088 /*
1089 ================
1090 G_RunItem
1091 
1092 ================
1093 */
G_RunItem(gentity_t * ent)1094 void G_RunItem( gentity_t *ent ) {
1095 	vec3_t		origin;
1096 	trace_t		tr;
1097 	int			contents;
1098 	int			mask;
1099 
1100 	// if groundentity has been set to -1, it may have been pushed off an edge
1101 	if ( ent->s.groundEntityNum == ENTITYNUM_NONE )
1102 	{
1103 		if ( ent->s.pos.trType != TR_GRAVITY )
1104 		{
1105 			ent->s.pos.trType = TR_GRAVITY;
1106 			ent->s.pos.trTime = level.time;
1107 		}
1108 	}
1109 
1110 	if ( ent->s.pos.trType == TR_STATIONARY )
1111 	{
1112 		// check think function
1113 		G_RunThink( ent );
1114 		if ( !g_gravity->value )
1115 		{
1116 			ent->s.pos.trType = TR_GRAVITY;
1117 			ent->s.pos.trTime = level.time;
1118 			ent->s.pos.trDelta[0] += Q_flrand(-1.0f, 1.0f) * 40.0f; // I dunno, just do this??
1119 			ent->s.pos.trDelta[1] += Q_flrand(-1.0f, 1.0f) * 40.0f;
1120 			ent->s.pos.trDelta[2] += Q_flrand(0.0f, 1.0f) * 20.0f;
1121 		}
1122 		return;
1123 	}
1124 
1125 	// get current position
1126 	EvaluateTrajectory( &ent->s.pos, level.time, origin );
1127 
1128 	// trace a line from the previous position to the current position
1129 	if ( ent->clipmask )
1130 	{
1131 		mask = ent->clipmask;
1132 	}
1133 	else
1134 	{
1135 		mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1136 	}
1137 
1138 	int ignore = ENTITYNUM_NONE;
1139 	if ( ent->owner )
1140 	{
1141 		ignore = ent->owner->s.number;
1142 	}
1143 	else if ( ent->activator )
1144 	{
1145 		ignore = ent->activator->s.number;
1146 	}
1147 	gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, G2_NOCOLLIDE, 0 );
1148 
1149 	VectorCopy( tr.endpos, ent->currentOrigin );
1150 
1151 	if ( tr.startsolid )
1152 	{
1153 		tr.fraction = 0;
1154 	}
1155 
1156 	gi.linkentity( ent );	// FIXME: avoid this for stationary?
1157 
1158 	// check think function
1159 	G_RunThink( ent );
1160 
1161 	if ( tr.fraction == 1 )
1162 	{
1163 		if ( g_gravity->value <= 0 )
1164 		{
1165 			if ( ent->s.apos.trType != TR_LINEAR )
1166 			{
1167 				VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1168 				ent->s.apos.trType = TR_LINEAR;
1169 				ent->s.apos.trDelta[1] = Q_flrand( -300, 300 );
1170 				ent->s.apos.trDelta[0] = Q_flrand( -10, 10 );
1171 				ent->s.apos.trDelta[2] = Q_flrand( -10, 10 );
1172 				ent->s.apos.trTime = level.time;
1173 			}
1174 		}
1175 		//friction in zero-G
1176 		if ( !g_gravity->value )
1177 		{
1178 			float friction = 0.975f;
1179 			/*friction -= ent->mass/1000.0f;
1180 			if ( friction < 0.1 )
1181 			{
1182 				friction = 0.1f;
1183 			}
1184 			*/
1185 			VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta );
1186 			VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1187 			ent->s.pos.trTime = level.time;
1188 		}
1189 		return;
1190 	}
1191 
1192 	// if it is in a nodrop volume, remove it
1193 	contents = gi.pointcontents( ent->currentOrigin, -1 );
1194 	if ( contents & CONTENTS_NODROP )
1195 	{
1196 		G_FreeEntity( ent );
1197 		return;
1198 	}
1199 
1200 	if ( !tr.startsolid )
1201 	{
1202 		G_BounceItem( ent, &tr );
1203 	}
1204 }
1205 
1206 /*
1207 ================
1208 ItemUse_Bacta
1209 
1210 ================
1211 */
ItemUse_Bacta(gentity_t * ent)1212 void ItemUse_Bacta(gentity_t *ent)
1213 {
1214 	if (!ent || !ent->client)
1215 	{
1216 		return;
1217 	}
1218 
1219 	if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] )
1220 	{
1221 		return;
1222 	}
1223 
1224 	ent->health += MAX_BACTA_HEAL_AMOUNT;
1225 
1226 	if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1227 	{
1228 		ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1229 	}
1230 
1231 	ent->client->ps.inventory[INV_BACTA_CANISTER]--;
1232 
1233 	G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
1234 }
1235