Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_items.c (25393B)


      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 Foobar; 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 
     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
    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 
    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 
    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 
    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 
    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 
    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 
    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 */
    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 */
    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 */
    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