1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 //
23 #include "g_local.h"
24 
25 /*
26 
27   Items are any object that a player can touch to gain some effect.
28 
29   Pickup will return the number of seconds until they should respawn.
30 
31   all items should pop when dropped in lava or slime
32 
33   Respawnable items don't actually go away when picked up, they are
34   just made invisible and untouchable.  This allows them to ride
35   movers and respawn apropriately.
36 */
37 
38 
39 #define	RESPAWN_ARMOR		25
40 #define	RESPAWN_HEALTH		35
41 #define	RESPAWN_AMMO		40
42 #define	RESPAWN_HOLDABLE	60
43 #define	RESPAWN_MEGAHEALTH	35//120
44 #define	RESPAWN_POWERUP		120
45 
46 
47 //======================================================================
48 
Pickup_Powerup(gentity_t * ent,gentity_t * other)49 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
50 	int			quantity;
51 	int			i;
52 	gclient_t	*client;
53 
54 	if ( !other->client->ps.powerups[ent->item->giTag] ) {
55 		// round timing to seconds to make multiple powerup timers
56 		// count in sync
57 		other->client->ps.powerups[ent->item->giTag] =
58 			level.time - ( level.time % 1000 );
59 	}
60 
61 	if ( ent->count ) {
62 		quantity = ent->count;
63 	} else {
64 		quantity = ent->item->quantity;
65 	}
66 
67 	other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
68 
69 	// give any nearby players a "denied" anti-reward
70 	for ( i = 0 ; i < level.maxclients ; i++ ) {
71 		vec3_t		delta;
72 		float		len;
73 		vec3_t		forward;
74 		trace_t		tr;
75 
76 		client = &level.clients[i];
77 		if ( client == other->client ) {
78 			continue;
79 		}
80 		if ( client->pers.connected == CON_DISCONNECTED ) {
81 			continue;
82 		}
83 		if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
84 			continue;
85 		}
86 
87     // if same team in team game, no sound
88     // cannot use OnSameTeam as it expects to g_entities, not clients
89   	if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam  ) {
90       continue;
91     }
92 
93 		// if too far away, no sound
94 		VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
95 		len = VectorNormalize( delta );
96 		if ( len > 192 ) {
97 			continue;
98 		}
99 
100 		// if not facing, no sound
101 		AngleVectors( client->ps.viewangles, forward, NULL, NULL );
102 		if ( DotProduct( delta, forward ) < 0.4 ) {
103 			continue;
104 		}
105 
106 		// if not line of sight, no sound
107 		trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
108 		if ( tr.fraction != 1.0 ) {
109 			continue;
110 		}
111 
112 		// anti-reward
113 		client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
114 	}
115 	return RESPAWN_POWERUP;
116 }
117 
118 //======================================================================
119 
120 #ifdef MISSIONPACK
Pickup_PersistantPowerup(gentity_t * ent,gentity_t * other)121 int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) {
122 	int		clientNum;
123 	char	userinfo[MAX_INFO_STRING];
124 	float	handicap;
125 	int		max;
126 
127 	other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist;
128 	other->client->persistantPowerup = ent;
129 
130 	switch( ent->item->giTag ) {
131 	case PW_GUARD:
132 		clientNum = other->client->ps.clientNum;
133 		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
134 		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
135 		if( handicap<=0.0f || handicap>100.0f) {
136 			handicap = 100.0f;
137 		}
138 		max = (int)(2 *  handicap);
139 
140 		other->health = max;
141 		other->client->ps.stats[STAT_HEALTH] = max;
142 		other->client->ps.stats[STAT_MAX_HEALTH] = max;
143 		other->client->ps.stats[STAT_ARMOR] = max;
144 		other->client->pers.maxHealth = max;
145 
146 		break;
147 
148 	case PW_SCOUT:
149 		clientNum = other->client->ps.clientNum;
150 		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
151 		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
152 		if( handicap<=0.0f || handicap>100.0f) {
153 			handicap = 100.0f;
154 		}
155 		other->client->pers.maxHealth = handicap;
156 		other->client->ps.stats[STAT_ARMOR] = 0;
157 		break;
158 
159 	case PW_DOUBLER:
160 		clientNum = other->client->ps.clientNum;
161 		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
162 		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
163 		if( handicap<=0.0f || handicap>100.0f) {
164 			handicap = 100.0f;
165 		}
166 		other->client->pers.maxHealth = handicap;
167 		break;
168 	case PW_AMMOREGEN:
169 		clientNum = other->client->ps.clientNum;
170 		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
171 		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
172 		if( handicap<=0.0f || handicap>100.0f) {
173 			handicap = 100.0f;
174 		}
175 		other->client->pers.maxHealth = handicap;
176 		memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes));
177 		break;
178 	default:
179 		clientNum = other->client->ps.clientNum;
180 		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
181 		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
182 		if( handicap<=0.0f || handicap>100.0f) {
183 			handicap = 100.0f;
184 		}
185 		other->client->pers.maxHealth = handicap;
186 		break;
187 	}
188 
189 	return -1;
190 }
191 
192 //======================================================================
193 #endif
194 
Pickup_Holdable(gentity_t * ent,gentity_t * other)195 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
196 
197 	other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
198 
199 	if( ent->item->giTag == HI_KAMIKAZE ) {
200 		other->client->ps.eFlags |= EF_KAMIKAZE;
201 	}
202 
203 	return RESPAWN_HOLDABLE;
204 }
205 
206 
207 //======================================================================
208 
Add_Ammo(gentity_t * ent,int weapon,int count)209 void Add_Ammo (gentity_t *ent, int weapon, int count)
210 {
211 	ent->client->ps.ammo[weapon] += count;
212 	if ( ent->client->ps.ammo[weapon] > 200 ) {
213 		ent->client->ps.ammo[weapon] = 200;
214 	}
215 }
216 
Pickup_Ammo(gentity_t * ent,gentity_t * other)217 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
218 {
219 	int		quantity;
220 
221 	if ( ent->count ) {
222 		quantity = ent->count;
223 	} else {
224 		quantity = ent->item->quantity;
225 	}
226 
227 	Add_Ammo (other, ent->item->giTag, quantity);
228 
229 	return RESPAWN_AMMO;
230 }
231 
232 //======================================================================
233 
234 
Pickup_Weapon(gentity_t * ent,gentity_t * other)235 int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
236 	int		quantity;
237 
238 	if ( ent->count < 0 ) {
239 		quantity = 0; // None for you, sir!
240 	} else {
241 		if ( ent->count ) {
242 			quantity = ent->count;
243 		} else {
244 			quantity = ent->item->quantity;
245 		}
246 
247 		// dropped items and teamplay weapons always have full ammo
248 		if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
249 			// respawning rules
250 			// drop the quantity if the already have over the minimum
251 			if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
252 				quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
253 			} else {
254 				quantity = 1;		// only add a single shot
255 			}
256 		}
257 	}
258 
259 	// add the weapon
260 	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
261 
262 	Add_Ammo( other, ent->item->giTag, quantity );
263 
264 	if (ent->item->giTag == WP_GRAPPLING_HOOK)
265 		other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo
266 
267 	// team deathmatch has slow weapon respawns
268 	if ( g_gametype.integer == GT_TEAM ) {
269 		return g_weaponTeamRespawn.integer;
270 	}
271 
272 	return g_weaponRespawn.integer;
273 }
274 
275 
276 //======================================================================
277 
Pickup_Health(gentity_t * ent,gentity_t * other)278 int Pickup_Health (gentity_t *ent, gentity_t *other) {
279 	int			max;
280 	int			quantity;
281 
282 	// small and mega healths will go over the max
283 #ifdef MISSIONPACK
284 	if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
285 		max = other->client->ps.stats[STAT_MAX_HEALTH];
286 	}
287 	else
288 #endif
289 	if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
290 		max = other->client->ps.stats[STAT_MAX_HEALTH];
291 	} else {
292 		max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
293 	}
294 
295 	if ( ent->count ) {
296 		quantity = ent->count;
297 	} else {
298 		quantity = ent->item->quantity;
299 	}
300 
301 	other->health += quantity;
302 
303 	if (other->health > max ) {
304 		other->health = max;
305 	}
306 	other->client->ps.stats[STAT_HEALTH] = other->health;
307 
308 	if ( ent->item->quantity == 100 ) {		// mega health respawns slow
309 		return RESPAWN_MEGAHEALTH;
310 	}
311 
312 	return RESPAWN_HEALTH;
313 }
314 
315 //======================================================================
316 
Pickup_Armor(gentity_t * ent,gentity_t * other)317 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
318 #ifdef MISSIONPACK
319 	int		upperBound;
320 
321 	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
322 
323 	if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
324 		upperBound = other->client->ps.stats[STAT_MAX_HEALTH];
325 	}
326 	else {
327 		upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
328 	}
329 
330 	if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) {
331 		other->client->ps.stats[STAT_ARMOR] = upperBound;
332 	}
333 #else
334 	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
335 	if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
336 		other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
337 	}
338 #endif
339 
340 	return RESPAWN_ARMOR;
341 }
342 
343 //======================================================================
344 
345 /*
346 ===============
347 RespawnItem
348 ===============
349 */
RespawnItem(gentity_t * ent)350 void RespawnItem( gentity_t *ent ) {
351 	// randomly select from teamed entities
352 	if (ent->team) {
353 		gentity_t	*master;
354 		int	count;
355 		int choice;
356 
357 		if ( !ent->teammaster ) {
358 			G_Error( "RespawnItem: bad teammaster");
359 		}
360 		master = ent->teammaster;
361 
362 		for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
363 			;
364 
365 		choice = rand() % count;
366 
367 		for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
368 			;
369 	}
370 
371 	ent->r.contents = CONTENTS_TRIGGER;
372 	ent->s.eFlags &= ~EF_NODRAW;
373 	ent->r.svFlags &= ~SVF_NOCLIENT;
374 	trap_LinkEntity (ent);
375 
376 	if ( ent->item->giType == IT_POWERUP ) {
377 		// play powerup spawn sound to all clients
378 		gentity_t	*te;
379 
380 		// if the powerup respawn sound should Not be global
381 		if (ent->speed) {
382 			te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
383 		}
384 		else {
385 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
386 		}
387 		te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );
388 		te->r.svFlags |= SVF_BROADCAST;
389 	}
390 
391 	if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) {
392 		// play powerup spawn sound to all clients
393 		gentity_t	*te;
394 
395 		// if the powerup respawn sound should Not be global
396 		if (ent->speed) {
397 			te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
398 		}
399 		else {
400 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
401 		}
402 		te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" );
403 		te->r.svFlags |= SVF_BROADCAST;
404 	}
405 
406 	// play the normal respawn sound only to nearby clients
407 	G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
408 
409 	ent->nextthink = 0;
410 }
411 
412 
413 /*
414 ===============
415 Touch_Item
416 ===============
417 */
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)418 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
419 	int			respawn;
420 	qboolean	predict;
421 
422 	if (!other->client)
423 		return;
424 	if (other->health < 1)
425 		return;		// dead people can't pickup
426 
427 	// the same pickup rules are used for client side and server side
428 	if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) {
429 		return;
430 	}
431 
432 	G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
433 
434 	predict = other->client->pers.predictItemPickup;
435 
436 	// call the item-specific pickup function
437 	switch( ent->item->giType ) {
438 	case IT_WEAPON:
439 		respawn = Pickup_Weapon(ent, other);
440 //		predict = qfalse;
441 		break;
442 	case IT_AMMO:
443 		respawn = Pickup_Ammo(ent, other);
444 //		predict = qfalse;
445 		break;
446 	case IT_ARMOR:
447 		respawn = Pickup_Armor(ent, other);
448 		break;
449 	case IT_HEALTH:
450 		respawn = Pickup_Health(ent, other);
451 		break;
452 	case IT_POWERUP:
453 		respawn = Pickup_Powerup(ent, other);
454 		predict = qfalse;
455 		break;
456 #ifdef MISSIONPACK
457 	case IT_PERSISTANT_POWERUP:
458 		respawn = Pickup_PersistantPowerup(ent, other);
459 		break;
460 #endif
461 	case IT_TEAM:
462 		respawn = Pickup_Team(ent, other);
463 		break;
464 	case IT_HOLDABLE:
465 		respawn = Pickup_Holdable(ent, other);
466 		break;
467 	default:
468 		return;
469 	}
470 
471 	if ( !respawn ) {
472 		return;
473 	}
474 
475 	// play the normal pickup sound
476 	if (predict) {
477 		G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
478 	} else {
479 		G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
480 	}
481 
482 	// powerup pickups are global broadcasts
483 	if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) {
484 		// if we want the global sound to play
485 		if (!ent->speed) {
486 			gentity_t	*te;
487 
488 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
489 			te->s.eventParm = ent->s.modelindex;
490 			te->r.svFlags |= SVF_BROADCAST;
491 		} else {
492 			gentity_t	*te;
493 
494 			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
495 			te->s.eventParm = ent->s.modelindex;
496 			// only send this temp entity to a single client
497 			te->r.svFlags |= SVF_SINGLECLIENT;
498 			te->r.singleClient = other->s.number;
499 		}
500 	}
501 
502 	// fire item targets
503 	G_UseTargets (ent, other);
504 
505 	// wait of -1 will not respawn
506 	if ( ent->wait == -1 ) {
507 		ent->r.svFlags |= SVF_NOCLIENT;
508 		ent->s.eFlags |= EF_NODRAW;
509 		ent->r.contents = 0;
510 		ent->unlinkAfterEvent = qtrue;
511 		return;
512 	}
513 
514 	// non zero wait overrides respawn time
515 	if ( ent->wait ) {
516 		respawn = ent->wait;
517 	}
518 
519 	// random can be used to vary the respawn time
520 	if ( ent->random ) {
521 		respawn += crandom() * ent->random;
522 		if ( respawn < 1 ) {
523 			respawn = 1;
524 		}
525 	}
526 
527 	// dropped items will not respawn
528 	if ( ent->flags & FL_DROPPED_ITEM ) {
529 		ent->freeAfterEvent = qtrue;
530 	}
531 
532 	// picked up items still stay around, they just don't
533 	// draw anything.  This allows respawnable items
534 	// to be placed on movers.
535 	ent->r.svFlags |= SVF_NOCLIENT;
536 	ent->s.eFlags |= EF_NODRAW;
537 	ent->r.contents = 0;
538 
539 	// ZOID
540 	// A negative respawn times means to never respawn this item (but don't
541 	// delete it).  This is used by items that are respawned by third party
542 	// events such as ctf flags
543 	if ( respawn <= 0 ) {
544 		ent->nextthink = 0;
545 		ent->think = 0;
546 	} else {
547 		ent->nextthink = level.time + respawn * 1000;
548 		ent->think = RespawnItem;
549 	}
550 	trap_LinkEntity( ent );
551 }
552 
553 
554 //======================================================================
555 
556 /*
557 ================
558 LaunchItem
559 
560 Spawns an item and tosses it forward
561 ================
562 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity)563 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
564 	gentity_t	*dropped;
565 
566 	dropped = G_Spawn();
567 
568 	dropped->s.eType = ET_ITEM;
569 	dropped->s.modelindex = item - bg_itemlist;	// store item number in modelindex
570 	dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
571 
572 	dropped->classname = item->classname;
573 	dropped->item = item;
574 	VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
575 	VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
576 	dropped->r.contents = CONTENTS_TRIGGER;
577 
578 	dropped->touch = Touch_Item;
579 
580 	G_SetOrigin( dropped, origin );
581 	dropped->s.pos.trType = TR_GRAVITY;
582 	dropped->s.pos.trTime = level.time;
583 	VectorCopy( velocity, dropped->s.pos.trDelta );
584 
585 	dropped->s.eFlags |= EF_BOUNCE_HALF;
586 #ifdef MISSIONPACK
587 	if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF)			&& item->giType == IT_TEAM) { // Special case for CTF flags
588 #else
589 	if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags
590 #endif
591 		dropped->think = Team_DroppedFlagThink;
592 		dropped->nextthink = level.time + 30000;
593 		Team_CheckDroppedItem( dropped );
594 	} else { // auto-remove after 30 seconds
595 		dropped->think = G_FreeEntity;
596 		dropped->nextthink = level.time + 30000;
597 	}
598 
599 	dropped->flags = FL_DROPPED_ITEM;
600 
601 	trap_LinkEntity (dropped);
602 
603 	return dropped;
604 }
605 
606 /*
607 ================
608 Drop_Item
609 
610 Spawns an item and tosses it forward
611 ================
612 */
613 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
614 	vec3_t	velocity;
615 	vec3_t	angles;
616 
617 	VectorCopy( ent->s.apos.trBase, angles );
618 	angles[YAW] += angle;
619 	angles[PITCH] = 0;	// always forward
620 
621 	AngleVectors( angles, velocity, NULL, NULL );
622 	VectorScale( velocity, 150, velocity );
623 	velocity[2] += 200 + crandom() * 50;
624 
625 	return LaunchItem( item, ent->s.pos.trBase, velocity );
626 }
627 
628 
629 /*
630 ================
631 Use_Item
632 
633 Respawn the item
634 ================
635 */
636 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
637 	RespawnItem( ent );
638 }
639 
640 //======================================================================
641 
642 /*
643 ================
644 FinishSpawningItem
645 
646 Traces down to find where an item should rest, instead of letting them
647 free fall from their spawn points
648 ================
649 */
650 void FinishSpawningItem( gentity_t *ent ) {
651 	trace_t		tr;
652 	vec3_t		dest;
653 
654 	VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
655 	VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
656 
657 	ent->s.eType = ET_ITEM;
658 	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
659 	ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
660 
661 	ent->r.contents = CONTENTS_TRIGGER;
662 	ent->touch = Touch_Item;
663 	// useing an item causes it to respawn
664 	ent->use = Use_Item;
665 
666 	if ( ent->spawnflags & 1 ) {
667 		// suspended
668 		G_SetOrigin( ent, ent->s.origin );
669 	} else {
670 		// drop to floor
671 		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
672 		trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
673 		if ( tr.startsolid ) {
674 			G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
675 			G_FreeEntity( ent );
676 			return;
677 		}
678 
679 		// allow to ride movers
680 		ent->s.groundEntityNum = tr.entityNum;
681 
682 		G_SetOrigin( ent, tr.endpos );
683 	}
684 
685 	// team slaves and targeted items aren't present at start
686 	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
687 		ent->s.eFlags |= EF_NODRAW;
688 		ent->r.contents = 0;
689 		return;
690 	}
691 
692 	// powerups don't spawn in for a while
693 	if ( ent->item->giType == IT_POWERUP ) {
694 		float	respawn;
695 
696 		respawn = 45 + crandom() * 15;
697 		ent->s.eFlags |= EF_NODRAW;
698 		ent->r.contents = 0;
699 		ent->nextthink = level.time + respawn * 1000;
700 		ent->think = RespawnItem;
701 		return;
702 	}
703 
704 
705 	trap_LinkEntity (ent);
706 }
707 
708 
709 qboolean	itemRegistered[MAX_ITEMS];
710 
711 /*
712 ==================
713 G_CheckTeamItems
714 ==================
715 */
716 void G_CheckTeamItems( void ) {
717 
718 	// Set up team stuff
719 	Team_InitGame();
720 
721 	if( g_gametype.integer == GT_CTF ) {
722 		gitem_t	*item;
723 
724 		// check for the two flags
725 		item = BG_FindItem( "Red Flag" );
726 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
727 			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" );
728 		}
729 		item = BG_FindItem( "Blue Flag" );
730 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
731 			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" );
732 		}
733 	}
734 #ifdef MISSIONPACK
735 	if( g_gametype.integer == GT_1FCTF ) {
736 		gitem_t	*item;
737 
738 		// check for all three flags
739 		item = BG_FindItem( "Red Flag" );
740 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
741 			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" );
742 		}
743 		item = BG_FindItem( "Blue Flag" );
744 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
745 			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" );
746 		}
747 		item = BG_FindItem( "Neutral Flag" );
748 		if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
749 			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_neutralflag in map" );
750 		}
751 	}
752 
753 	if( g_gametype.integer == GT_OBELISK ) {
754 		gentity_t	*ent;
755 
756 		// check for the two obelisks
757 		ent = NULL;
758 		ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
759 		if( !ent ) {
760 			G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" );
761 		}
762 
763 		ent = NULL;
764 		ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
765 		if( !ent ) {
766 			G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" );
767 		}
768 	}
769 
770 	if( g_gametype.integer == GT_HARVESTER ) {
771 		gentity_t	*ent;
772 
773 		// check for all three obelisks
774 		ent = NULL;
775 		ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
776 		if( !ent ) {
777 			G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" );
778 		}
779 
780 		ent = NULL;
781 		ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
782 		if( !ent ) {
783 			G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" );
784 		}
785 
786 		ent = NULL;
787 		ent = G_Find( ent, FOFS(classname), "team_neutralobelisk" );
788 		if( !ent ) {
789 			G_Printf( S_COLOR_YELLOW "WARNING: No team_neutralobelisk in map" );
790 		}
791 	}
792 #endif
793 }
794 
795 /*
796 ==============
797 ClearRegisteredItems
798 ==============
799 */
800 void ClearRegisteredItems( void ) {
801 	memset( itemRegistered, 0, sizeof( itemRegistered ) );
802 
803 	// players always start with the base weapon
804 	RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
805 	RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
806 #ifdef MISSIONPACK
807 	if( g_gametype.integer == GT_HARVESTER ) {
808 		RegisterItem( BG_FindItem( "Red Cube" ) );
809 		RegisterItem( BG_FindItem( "Blue Cube" ) );
810 	}
811 #endif
812 }
813 
814 /*
815 ===============
816 RegisterItem
817 
818 The item will be added to the precache list
819 ===============
820 */
821 void RegisterItem( gitem_t *item ) {
822 	if ( !item ) {
823 		G_Error( "RegisterItem: NULL" );
824 	}
825 	itemRegistered[ item - bg_itemlist ] = qtrue;
826 }
827 
828 
829 /*
830 ===============
831 SaveRegisteredItems
832 
833 Write the needed items to a config string
834 so the client will know which ones to precache
835 ===============
836 */
837 void SaveRegisteredItems( void ) {
838 	char	string[MAX_ITEMS+1];
839 	int		i;
840 	int		count;
841 
842 	count = 0;
843 	for ( i = 0 ; i < bg_numItems ; i++ ) {
844 		if ( itemRegistered[i] ) {
845 			count++;
846 			string[i] = '1';
847 		} else {
848 			string[i] = '0';
849 		}
850 	}
851 	string[ bg_numItems ] = 0;
852 
853 	G_Printf( "%i items registered\n", count );
854 	trap_SetConfigstring(CS_ITEMS, string);
855 }
856 
857 /*
858 ============
859 G_ItemDisabled
860 ============
861 */
862 int G_ItemDisabled( gitem_t *item ) {
863 
864 	char name[128];
865 
866 	Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
867 	return trap_Cvar_VariableIntegerValue( name );
868 }
869 
870 /*
871 ============
872 G_SpawnItem
873 
874 Sets the clipping size and plants the object on the floor.
875 
876 Items can't be immediately dropped to floor, because they might
877 be on an entity that hasn't spawned yet.
878 ============
879 */
880 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
881 	G_SpawnFloat( "random", "0", &ent->random );
882 	G_SpawnFloat( "wait", "0", &ent->wait );
883 
884 	RegisterItem( item );
885 	if ( G_ItemDisabled(item) )
886 		return;
887 
888 	ent->item = item;
889 	// some movers spawn on the second frame, so delay item
890 	// spawns until the third frame so they can ride trains
891 	ent->nextthink = level.time + FRAMETIME * 2;
892 	ent->think = FinishSpawningItem;
893 
894 	ent->physicsBounce = 0.50;		// items are bouncy
895 
896 	if ( item->giType == IT_POWERUP ) {
897 		G_SoundIndex( "sound/items/poweruprespawn.wav" );
898 		G_SpawnFloat( "noglobalsound", "0", &ent->speed);
899 	}
900 
901 #ifdef MISSIONPACK
902 	if ( item->giType == IT_PERSISTANT_POWERUP ) {
903 		ent->s.generic1 = ent->spawnflags;
904 	}
905 #endif
906 }
907 
908 
909 /*
910 ================
911 G_BounceItem
912 
913 ================
914 */
915 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
916 	vec3_t	velocity;
917 	float	dot;
918 	int		hitTime;
919 
920 	// reflect the velocity on the trace plane
921 	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
922 	BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
923 	dot = DotProduct( velocity, trace->plane.normal );
924 	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
925 
926 	// cut the velocity to keep from bouncing forever
927 	VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
928 
929 	// check for stop
930 	if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
931 		trace->endpos[2] += 1.0;	// make sure it is off ground
932 		SnapVector( trace->endpos );
933 		G_SetOrigin( ent, trace->endpos );
934 		ent->s.groundEntityNum = trace->entityNum;
935 		return;
936 	}
937 
938 	VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
939 	VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
940 	ent->s.pos.trTime = level.time;
941 }
942 
943 
944 /*
945 ================
946 G_RunItem
947 
948 ================
949 */
950 void G_RunItem( gentity_t *ent ) {
951 	vec3_t		origin;
952 	trace_t		tr;
953 	int			contents;
954 	int			mask;
955 
956 	// if groundentity has been set to -1, it may have been pushed off an edge
957 	if ( ent->s.groundEntityNum == -1 ) {
958 		if ( ent->s.pos.trType != TR_GRAVITY ) {
959 			ent->s.pos.trType = TR_GRAVITY;
960 			ent->s.pos.trTime = level.time;
961 		}
962 	}
963 
964 	if ( ent->s.pos.trType == TR_STATIONARY ) {
965 		// check think function
966 		G_RunThink( ent );
967 		return;
968 	}
969 
970 	// get current position
971 	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
972 
973 	// trace a line from the previous position to the current position
974 	if ( ent->clipmask ) {
975 		mask = ent->clipmask;
976 	} else {
977 		mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
978 	}
979 	trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
980 		ent->r.ownerNum, mask );
981 
982 	VectorCopy( tr.endpos, ent->r.currentOrigin );
983 
984 	if ( tr.startsolid ) {
985 		tr.fraction = 0;
986 	}
987 
988 	trap_LinkEntity( ent );	// FIXME: avoid this for stationary?
989 
990 	// check think function
991 	G_RunThink( ent );
992 
993 	if ( tr.fraction == 1 ) {
994 		return;
995 	}
996 
997 	// if it is in a nodrop volume, remove it
998 	contents = trap_PointContents( ent->r.currentOrigin, -1 );
999 	if ( contents & CONTENTS_NODROP ) {
1000 		if (ent->item && ent->item->giType == IT_TEAM) {
1001 			Team_FreeEntity(ent);
1002 		} else {
1003 			G_FreeEntity( ent );
1004 		}
1005 		return;
1006 	}
1007 
1008 	G_BounceItem( ent, &tr );
1009 }
1010 
1011