/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
#include "g_local.h"

/*

  Items are any object that a player can touch to gain some effect.

  Pickup will return the number of seconds until they should respawn.

  all items should pop when dropped in lava or slime

  Respawnable items don't actually go away when picked up, they are
  just made invisible and untouchable.  This allows them to ride
  movers and respawn apropriately.
*/


//======================================================================

int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
	int			quantity;
#ifndef TURTLEARENA // AWARDS
	int			i;
	gclient_t	*client;
#endif

	if ( !other->client->ps.powerups[ent->item->giTag] ) {
		// round timing to seconds to make multiple powerup timers
		// count in sync
		other->client->ps.powerups[ent->item->giTag] = 
			level.time - ( level.time % 1000 );
	}

	if ( ent->count ) {
		quantity = ent->count;
	} else {
		quantity = ent->item->quantity;
	}

	other->client->ps.powerups[ent->item->giTag] += quantity * 1000;

#ifndef TURTLEARENA // AWARDS
	// give any nearby players a "denied" anti-reward
	for ( i = 0 ; i < level.maxclients ; i++ ) {
		vec3_t		delta;
		float		len;
		vec3_t		forward;
		trace_t		tr;

		client = &level.clients[i];
		if ( client == other->client ) {
			continue;
		}
		if ( client->pers.connected == CON_DISCONNECTED ) {
			continue;
		}
		if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
			continue;
		}

    // if same team in team game, no sound
    // cannot use OnSameTeam as it expects to g_entities, not clients
  	if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam  ) {
      continue;
    }

		// if too far away, no sound
		VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
		len = VectorNormalize( delta );
		if ( len > 192 ) {
			continue;
		}

		// if not facing, no sound
		AngleVectors( client->ps.viewangles, forward, NULL, NULL );
		if ( DotProduct( delta, forward ) < 0.4 ) {
			continue;
		}

		// if not line of sight, no sound
		trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
		if ( tr.fraction != 1.0 ) {
			continue;
		}

		// anti-reward
		client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
	}
#endif
	return RESPAWN_POWERUP;
}

//======================================================================

#ifdef MISSIONPACK
int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) {
	int		clientNum;
	char	userinfo[MAX_INFO_STRING];
	float	handicap;
	int		max;

#ifdef IOQ3ZTM
	other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ITEM_INDEX(ent->item);
#else
	other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist;
#endif
	other->client->persistantPowerup = ent;

	switch( ent->item->giTag ) {
	case PW_GUARD:
		clientNum = other->client->ps.clientNum;
		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
		if( handicap<=0.0f || handicap>100.0f) {
			handicap = 100.0f;
		}
		max = (int)(2 *  handicap);

		other->health = max;
		other->client->ps.stats[STAT_HEALTH] = max;
		other->client->ps.stats[STAT_MAX_HEALTH] = max;
#ifndef TURTLEARENA // NOARMOR
		other->client->ps.stats[STAT_ARMOR] = max;
#endif
		other->client->pers.maxHealth = max;

		break;

	case PW_SCOUT:
		clientNum = other->client->ps.clientNum;
		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
		if( handicap<=0.0f || handicap>100.0f) {
			handicap = 100.0f;
		}
#ifdef TURTLEARENA // Only half as much health.
		max = (int)(0.5f *  handicap);

		other->health = max;
		other->client->ps.stats[STAT_HEALTH] = max;
		other->client->ps.stats[STAT_MAX_HEALTH] = max;
		other->client->pers.maxHealth = max;
#else
		other->client->pers.maxHealth = handicap;
#endif
#ifndef TURTLEARENA // NOARMOR
		other->client->ps.stats[STAT_ARMOR] = 0;
#endif
		break;

	case PW_DOUBLER:
		clientNum = other->client->ps.clientNum;
		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
		if( handicap<=0.0f || handicap>100.0f) {
			handicap = 100.0f;
		}
		other->client->pers.maxHealth = handicap;
		break;
	case PW_AMMOREGEN:
		clientNum = other->client->ps.clientNum;
		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
		if( handicap<=0.0f || handicap>100.0f) {
			handicap = 100.0f;
		}
		other->client->pers.maxHealth = handicap;
		memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes));
		break;
	default:
		clientNum = other->client->ps.clientNum;
		trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
		handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
		if( handicap<=0.0f || handicap>100.0f) {
			handicap = 100.0f;
		}
		other->client->pers.maxHealth = handicap;
		break;
	}

	return -1;
}

//======================================================================
#endif

int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
#ifdef TA_HOLDSYS
	other->client->ps.holdableIndex = ent->item->giTag;

	if (ent->item->quantity == 0)
		other->client->ps.holdable[other->client->ps.holdableIndex] = 1;
	else
		other->client->ps.holdable[other->client->ps.holdableIndex] += ent->item->quantity;

	if (other->client->ps.holdable[other->client->ps.holdableIndex] > MAX_SHURIKENS)
	{
		other->client->ps.holdable[other->client->ps.holdableIndex] = MAX_SHURIKENS;
	}
#else
#ifdef IOQ3ZTM
	other->client->ps.stats[STAT_HOLDABLE_ITEM] = ITEM_INDEX(ent->item);
#else
	other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
#endif
#endif

#ifndef TURTLEARENA // NO_KAMIKAZE_ITEM
	if( ent->item->giTag == HI_KAMIKAZE ) {
		other->client->ps.eFlags |= EF_KAMIKAZE;
	}
#endif

	return RESPAWN_HOLDABLE;
}


//======================================================================

void Add_Ammo (gentity_t *ent, int weapon, int count)
{
#ifdef TA_WEAPSYS_EX
	int stat = STAT_AMMO;

	// Not ammo for are current weapon
	if (weapon != ent->client->ps.weapon)
	{
		// When picking up a new weapon we haven't change weapons yet when we get the ammo.
		if (weapon == ent->client->ps.stats[STAT_PENDING_WEAPON]) {
			stat = STAT_PENDING_AMMO;
		}
		// Got ammo for weapon we are about to drop?
		//  I haven't had this happen but it could if there was ammo items.
		else if (weapon == ent->client->ps.stats[STAT_DROP_WEAPON]) {
			stat = STAT_DROP_AMMO;
		}
		else {
			G_Printf("DEBUG: Add_Ammo: Player got ammo for a weapon they don't own! weaponNum=%d, ammo_count=%d\n", weapon, count);
			return;
		}
	}
#endif
#ifdef TA_WEAPSYS // Don't give ammo to melee weapons
	if ( !BG_WeapUseAmmo(weapon) )
	{
		// unlimited ammo
#ifdef TA_WEAPSYS_EX
		ent->client->ps.stats[stat] = -1;
#else
		ent->client->ps.ammo[weapon] = -1;
#endif
		return;
	}
#ifdef TA_WEAPSYS_EX
	if (ent->client->ps.stats[stat] == -1)
		ent->client->ps.stats[stat] = 0;
#else
	if (ent->client->ps.ammo[weapon] == -1)
		ent->client->ps.ammo[weapon] = 0;
#endif
#endif // TA_WEAPSYS
#ifdef TA_WEAPSYS_EX
	ent->client->ps.stats[stat] += count;
	if ( ent->client->ps.stats[stat] > 200 ) {
		ent->client->ps.stats[stat] = 200;
	}
#else
	ent->client->ps.ammo[weapon] += count;
	if ( ent->client->ps.ammo[weapon] > 200 ) {
		ent->client->ps.ammo[weapon] = 200;
	}
#endif
}

int Pickup_Ammo (gentity_t *ent, gentity_t *other)
{
	int		quantity;

	if ( ent->count ) {
		quantity = ent->count;
	} else {
		quantity = ent->item->quantity;
	}

	Add_Ammo (other, ent->item->giTag, quantity);

	return RESPAWN_AMMO;
}

//======================================================================


int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
	int		quantity;
#ifdef TA_WEAPSYS
	int		weaponNum;

	if ( ent->item->giTag == WP_DEFAULT) {
		weaponNum = other->client->ps.stats[STAT_DEFAULTWEAPON];
	} else {
		weaponNum = ent->item->giTag;
	}
#endif

	if ( ent->count < 0 ) {
		quantity = 0; // None for you, sir!
	} else {
		if ( ent->count ) {
			quantity = ent->count;
		} else {
			quantity = ent->item->quantity;
		}

#if defined TA_WEAPSYS && defined TURTLEARENA
		// Dropped items have the ammo from the player who dropped it
		//   so don't mess with it.
		if ( !(ent->flags & FL_DROPPED_ITEM) )
#else
		// dropped items and teamplay weapons always have full ammo
		if ( !(ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM )
#endif
		{
			// respawning rules
			// drop the quantity if the already have over the minimum
#ifdef TA_WEAPSYS_EX
			int stat = STAT_AMMO;

			// Not ammo for are current weapon
			if (weaponNum != other->client->ps.weapon)
			{
				// When picking up a new weapon we haven't change weapons yet when we get the ammo.
				stat = STAT_PENDING_AMMO;
			}

			if ( other->client->ps.stats[stat] < quantity) {
				quantity = quantity - other->client->ps.stats[stat];
				if (other->client->ps.stats[stat] == -1) {
					quantity--;
				}
			} else {
				quantity = 1;		// only add a single shot
			}
#else
#ifdef TA_WEAPSYS
			if ( other->client->ps.ammo[ weaponNum ] < quantity ) {
				quantity = quantity - other->client->ps.ammo[ weaponNum ];
				if (other->client->ps.ammo[ weaponNum ] == -1) {
					quantity--;
				}
			}
#else
			if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
				quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
		}
#endif
			else {
				quantity = 1;		// only add a single shot
			}
#endif
		}
	}

#ifdef TA_WEAPSYS_EX
	// change to weapon
	other->client->ps.stats[STAT_PENDING_WEAPON] = weaponNum;
#else
	// add the weapon
#ifdef TA_WEAPSYS
	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << weaponNum );
#else
	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
#endif
#endif

#ifdef TA_WEAPSYS
	Add_Ammo( other, weaponNum, quantity );
#else
	Add_Ammo( other, ent->item->giTag, quantity );
#endif

#ifndef TA_WEAPSYS
	if (ent->item->giTag == WP_GRAPPLING_HOOK)
		other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo
#endif

	// team deathmatch has slow weapon respawns
	if ( g_gametype.integer == GT_TEAM ) {
		return g_weaponTeamRespawn.integer;
	}

	return g_weaponRespawn.integer;
}


//======================================================================

int Pickup_Health (gentity_t *ent, gentity_t *other) {
	int			max;
	int			quantity;

	// small and mega healths will go over the max
#ifdef MISSIONPACK
#ifdef TA_ITEMSYS
	if( BG_ItemForItemNum(other->client->ps.stats[STAT_PERSISTANT_POWERUP])->giTag == PW_GUARD )
#else
	if( bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD )
#endif
	{
		max = other->client->ps.stats[STAT_MAX_HEALTH];
	}
	else
#endif
#ifndef TURTLEARENA // Treat all health items the same
	if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
		max = other->client->ps.stats[STAT_MAX_HEALTH];
	}
	else
#endif
	{
		max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
	}

	if ( ent->count ) {
		quantity = ent->count;
	} else {
		quantity = ent->item->quantity;
	}

#ifdef TURTLEARENA // Guards having health regen makes them pretty much unkillable.
			// So get 2x health from pickups!
#ifdef TA_ITEMSYS
	if( other->client && BG_ItemForItemNum(other->client->ps.stats[STAT_PERSISTANT_POWERUP])->giTag == PW_GUARD )
#else
	if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD )
#endif
	{
		other->health += quantity*2;
	}
	else
	// And cut health that scout gets?
#ifdef TA_ITEMSYS
	if( other->client && BG_ItemForItemNum(other->client->ps.stats[STAT_PERSISTANT_POWERUP])->giTag == PW_SCOUT )
#else
	if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT )
#endif
	{
		other->health += quantity*0.5f;
	}
	else
#endif
	other->health += quantity;

	if (other->health > max ) {
		other->health = max;
	}
	other->client->ps.stats[STAT_HEALTH] = other->health;

	if ( ent->item->quantity == 100 ) {		// mega health respawns slow
		return RESPAWN_MEGAHEALTH;
	}

	return RESPAWN_HEALTH;
}

#ifndef TURTLEARENA // NOARMOR
//======================================================================

int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
#ifdef MISSIONPACK
	int		upperBound;

	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;

#ifdef TA_ITEMSYS
	if( other->client && BG_ItemForItemNum(other->client->ps.stats[STAT_PERSISTANT_POWERUP])->giTag == PW_GUARD )
#else
	if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD )
#endif
	{
		upperBound = other->client->ps.stats[STAT_MAX_HEALTH];
	}
	else {
		upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
	}

	if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) {
		other->client->ps.stats[STAT_ARMOR] = upperBound;
	}
#else
	other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
	if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
		other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
	}
#endif

	return RESPAWN_ARMOR;
}
#endif

#ifdef TURTLEARENA // NIGHTS_ITEMS
//======================================================================

int Pickup_Score( gentity_t *ent, gentity_t *other ) {
	int mult;
	int count;
	int score;

	// Increase score chain
	if (other->client->ps.chain < 999) {
		other->client->ps.chain++;
	}
	other->client->ps.chainTime = 2000;

	// Max multipler of 10
	mult = other->client->ps.chain;
	if (mult > 10) {
		mult = 10;
	}

	if (ent->count) {
		count = ent->count;
	} else {
		count = ent->item->quantity;
	}

	score = count * mult;

	AddScoreEx(other, ent->r.currentOrigin, score, other->client->ps.chain);

	if (ent->item->giTag == 2) {
		// Spheres
		other->client->ps.stats[STAT_SPHERES] += 1;
	} else if (ent->item->giTag == 1) {
		// Stars
		other->client->ps.stats[STAT_STARS] += 1;
	}

	return RESPAWN_SCORE;
}
#endif

//======================================================================

/*
===============
RespawnItem
===============
*/
void RespawnItem( gentity_t *ent ) {
	// randomly select from teamed entities
	if (ent->team) {
		gentity_t	*master;
		int	count;
		int choice;

		if ( !ent->teammaster ) {
			G_Error( "RespawnItem: bad teammaster");
		}
		master = ent->teammaster;

		for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
			;

		choice = rand() % count;

		for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
			;
	}

#ifdef TA_WEAPSYS
	// weapon_random: Change item!
	if (ent->item->giType == IT_WEAPON && ent->s.eFlags & EF_VOTED)
	{
		gitem_t *item;

		item = G_RandomWeaponItem(ent, ent->spawnflags>>1);
		if (item) {
			ent->item = item;
			ent->s.modelindex = ITEM_INDEX(item);
		}
	}
#endif
	ent->s.contents = CONTENTS_TRIGGER;
	ent->s.eFlags &= ~EF_NODRAW;
	ent->r.svFlags &= ~SVF_NOCLIENT;
	trap_LinkEntity (ent);

	if ( ent->item->giType == IT_POWERUP ) {
		// play powerup spawn sound to all clients
		gentity_t	*te;

		// if the powerup respawn sound should Not be global
		if (ent->speed) {
			te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
		}
		else {
			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
		}
		te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );
		te->r.svFlags |= SVF_BROADCAST;
	}

#ifndef TURTLEARENA // NO_KAMIKAZE_ITEM
	if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) {
		// play powerup spawn sound to all clients
		gentity_t	*te;

		// if the powerup respawn sound should Not be global
		if (ent->speed) {
			te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
		}
		else {
			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
		}
		te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" );
		te->r.svFlags |= SVF_BROADCAST;
	}
#endif

	// play the normal respawn sound only to nearby clients
	G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );

	ent->nextthink = 0;
}


/*
===============
Touch_Item
===============
*/
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
	int			respawn;
	qboolean	predict;

	if (!other->client)
		return;
	if (other->health < 1)
		return;		// dead people can't pickup

#if 0 //#ifdef TA_WEAPSYS // AUTO_DROP_WEAPON
	if (ent->item->giType == IT_WEAPON
		&& other->client->ps.weapon != other->client->ps.stats[STAT_DEFAULTWEAPON]
		&& other->client->ps.weapon != ent->item->giTag
		&& !(other->client->ps.pm_flags & PMF_GRAPPLE_PULL))
	{
		// Start drop!
		other->client->ps.stats[STAT_PENDING_WEAPON] = other->client->ps.stats[STAT_DEFAULTWEAPON];
		return;
	}
#endif

	// the same pickup rules are used for client side and server side
	if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) {
		return;
	}

	G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );

	predict = other->client->pers.predictItemPickup;

	// call the item-specific pickup function
	switch( ent->item->giType ) {
	case IT_WEAPON:
		respawn = Pickup_Weapon(ent, other);
//		predict = qfalse;
		break;
	case IT_AMMO:
		respawn = Pickup_Ammo(ent, other);
//		predict = qfalse;
		break;
#ifdef TURTLEARENA // NIGHTS_ITEMS
	case IT_SCORE:
		respawn = Pickup_Score(ent, other);
		break;
#endif
#ifndef TURTLEARENA // NOARMOR
	case IT_ARMOR:
		respawn = Pickup_Armor(ent, other);
		break;
#endif
	case IT_HEALTH:
		respawn = Pickup_Health(ent, other);
		break;
	case IT_POWERUP:
		respawn = Pickup_Powerup(ent, other);
		predict = qfalse;
		break;
#ifdef MISSIONPACK
	case IT_PERSISTANT_POWERUP:
		respawn = Pickup_PersistantPowerup(ent, other);
		break;
#endif
	case IT_TEAM:
		respawn = Pickup_Team(ent, other);
		break;
	case IT_HOLDABLE:
		respawn = Pickup_Holdable(ent, other);
		break;
	default:
		return;
	}

	if ( !respawn ) {
		return;
	}

	// play the normal pickup sound
	if (predict) {
		G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
	} else {
		G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
	}

	// powerup pickups are global broadcasts
	if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) {
		// if we want the global sound to play
		if (!ent->speed) {
			gentity_t	*te;

			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
			te->s.eventParm = ent->s.modelindex;
			te->r.svFlags |= SVF_BROADCAST;
		} else {
			gentity_t	*te;

			te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
			te->s.eventParm = ent->s.modelindex;
			// only send this temp entity to a single client
			te->r.svFlags |= SVF_SINGLECLIENT;
			te->r.singleClient = other->s.number;
		}
	}

	// fire item targets
	G_UseTargets (ent, other);

	// wait of -1 will not respawn
	if ( ent->wait == -1 ) {
		ent->r.svFlags |= SVF_NOCLIENT;
		ent->s.eFlags |= EF_NODRAW;
		ent->s.contents = 0;
		ent->unlinkAfterEvent = qtrue;
		return;
	}

	// non zero wait overrides respawn time
	if ( ent->wait ) {
		respawn = ent->wait;
	}

	// random can be used to vary the respawn time
	if ( ent->random ) {
		respawn += crandom() * ent->random;
		if ( respawn < 1 ) {
			respawn = 1;
		}
	}

	// dropped items will not respawn
	if ( ent->flags & FL_DROPPED_ITEM ) {
		ent->freeAfterEvent = qtrue;
	}

	// picked up items still stay around, they just don't
	// draw anything.  This allows respawnable items
	// to be placed on movers.
	ent->r.svFlags |= SVF_NOCLIENT;
	ent->s.eFlags |= EF_NODRAW;
	ent->s.contents = 0;

	// ZOID
	// A negative respawn times means to never respawn this item (but don't 
	// delete it).  This is used by items that are respawned by third party 
	// events such as ctf flags
	if ( respawn <= 0 ) {
		ent->nextthink = 0;
		ent->think = 0;
	} else {
		ent->nextthink = level.time + respawn * 1000;
		ent->think = RespawnItem;
	}
	trap_LinkEntity( ent );
}


//======================================================================

/*
================
LaunchItem

Spawns an item and tosses it forward
================
*/
gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
	gentity_t	*dropped;

	dropped = G_Spawn();

	dropped->s.eType = ET_ITEM;
#ifdef IOQ3ZTM
	dropped->s.modelindex = ITEM_INDEX(item);	// store item number in modelindex
#else
	dropped->s.modelindex = item - bg_itemlist;	// store item number in modelindex
#endif
	dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item

	dropped->classname = item->classname;
	dropped->item = item;
	VectorSet (dropped->s.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
	VectorSet (dropped->s.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
	dropped->s.contents = CONTENTS_TRIGGER;

	dropped->touch = Touch_Item;

	G_SetOrigin( dropped, origin );
	dropped->s.pos.trType = TR_GRAVITY;
	dropped->s.pos.trTime = level.time;
	VectorCopy( velocity, dropped->s.pos.trDelta );

	dropped->s.eFlags |= EF_BOUNCE_HALF;
#ifdef MISSIONPACK
	if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF)			&& item->giType == IT_TEAM) { // Special case for CTF flags
#else
	if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags
#endif
		dropped->think = Team_DroppedFlagThink;
		dropped->nextthink = level.time + 30000;
		Team_CheckDroppedItem( dropped );
	} else { // auto-remove after 30 seconds
		dropped->think = G_FreeEntity;
		dropped->nextthink = level.time + 30000;
	}

#ifdef IOQ3ZTM // ITEMS_DISAPPEAR
	dropped->s.frame = 30000;
#endif

	dropped->flags = FL_DROPPED_ITEM;

	trap_LinkEntity (dropped);

#ifdef IOQ3ZTM // ITEMS_DISAPPEAR
	// Update cent->miscTime
	G_AddEvent( dropped, EV_ITEM_RESPAWN, 1 );
#endif

	return dropped;
}

/*
================
Drop_Item

Spawns an item and tosses it forward
================
*/
gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
	vec3_t	velocity;
	vec3_t	angles;
	gentity_t *drop;

	VectorCopy( ent->s.apos.trBase, angles );
	angles[YAW] += angle;
	angles[PITCH] = 0;	// always forward

	AngleVectors( angles, velocity, NULL, NULL );
	VectorScale( velocity, 150, velocity );
	velocity[2] += 200 + crandom() * 50;

	drop = LaunchItem( item, ent->s.pos.trBase, velocity );

#ifdef TURTLEARENA
	// Dropped CTF flag
#ifdef MISSIONPACK
	if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF) && item->giType == IT_TEAM) {
#else
	if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) {
#endif
		if (ent->client) {
			// Flip CTF flag when going backward
			if (ent->client->ps.pm_flags & PMF_BACKWARDS_RUN) {
				angles[YAW] += 180;
			}

			// Show message when client drops CTF flag.
			if( item->giTag == PW_NEUTRALFLAG ) {
				PrintMsg (NULL, "%s" S_COLOR_WHITE " dropped the flag!\n", ent->client->pers.netname );
			} else {
				PrintMsg (NULL, "%s" S_COLOR_WHITE " dropped the %s flag!\n",
					ent->client->pers.netname, TeamNameInColor(item->giTag == PW_REDFLAG ? TEAM_RED : TEAM_BLUE));
			}
		}
	}
#endif

#ifdef IOQ3ZTM
	// Save angles to allow cgame to render using real angles.
	VectorCopy(angles, drop->s.angles);
#endif

#ifdef IOQ3ZTM // DROP_ITEM_FIX
	if (ent->client) {
		// Save the player who drop the item, so we can wait till the
		//  player isn't touching it to allow them to pick it up.
		//  Becuase otherwise they pickup the item as soon as they drop it.
		drop->s.generic1 = ent->client->ps.clientNum+1;
	}
	drop->s.time2 = level.time + 1000;
#endif

	return drop;
}


/*
================
Use_Item

Respawn the item
================
*/
void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	RespawnItem( ent );
}

//======================================================================

/*
================
FinishSpawningItem

Traces down to find where an item should rest, instead of letting them
free fall from their spawn points
================
*/
void FinishSpawningItem( gentity_t *ent ) {
	trace_t		tr;
	vec3_t		dest;

	VectorSet( ent->s.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
	VectorSet( ent->s.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );

	ent->s.eType = ET_ITEM;
#ifdef IOQ3ZTM
	ent->s.modelindex = ITEM_INDEX(ent->item);		// store item number in modelindex
#else
	ent->s.modelindex = ent->item - bg_itemlist;		// store item number in modelindex
#endif
	ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item

	ent->s.contents = CONTENTS_TRIGGER;
	ent->touch = Touch_Item;
	// useing an item causes it to respawn
	ent->use = Use_Item;

	if ( ent->spawnflags & 1 ) {
		// suspended
		G_SetOrigin( ent, ent->s.origin );
	} else {
		// drop to floor
		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
		trap_Trace( &tr, ent->s.origin, ent->s.mins, ent->s.maxs, dest, ent->s.number, MASK_SOLID );
		if ( tr.startsolid ) {
			G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
			G_FreeEntity( ent );
			return;
		}

		// allow to ride movers
		ent->s.groundEntityNum = tr.entityNum;

		G_SetOrigin( ent, tr.endpos );
	}

	// team slaves and targeted items aren't present at start
	if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
		ent->s.eFlags |= EF_NODRAW;
		ent->s.contents = 0;
		return;
	}

	// powerups don't spawn in for a while
	if ( ent->item->giType == IT_POWERUP
#ifdef NIGHTSMODE
		&& ent->item->giTag != PW_FLIGHT
#endif
	) {
		float	respawn;

		respawn = 45 + crandom() * 15;
		ent->s.eFlags |= EF_NODRAW;
		ent->s.contents = 0;
		ent->nextthink = level.time + respawn * 1000;
		ent->think = RespawnItem;
		return;
	}


	trap_LinkEntity (ent);
}


qboolean	itemRegistered[MAX_ITEMS];

/*
==================
G_CheckTeamItems
==================
*/
void G_CheckTeamItems( void ) {

	// Set up team stuff
	Team_InitGame();

	if( g_gametype.integer == GT_CTF ) {
		gitem_t	*item;

		// check for the two flags
		item = BG_FindItem( "Red Flag" );
#ifdef IOQ3ZTM
		if ( !item || !itemRegistered[ ITEM_INDEX(item) ] )
#else
		if ( !item || !itemRegistered[ item - bg_itemlist ] )
#endif
		{
			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map\n" );
		}
		item = BG_FindItem( "Blue Flag" );
#ifdef IOQ3ZTM
		if ( !item || !itemRegistered[ ITEM_INDEX(item) ] )
#else
		if ( !item || !itemRegistered[ item - bg_itemlist ] )
#endif
		{
			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map\n" );
		}
	}
#ifdef MISSIONPACK
	if( g_gametype.integer == GT_1FCTF ) {
		gitem_t	*item;

		// check for all three flags
		item = BG_FindItem( "Red Flag" );
#ifdef IOQ3ZTM
		if ( !item || !itemRegistered[ ITEM_INDEX(item) ] )
#else
		if ( !item || !itemRegistered[ item - bg_itemlist ] )
#endif
		{
			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map\n" );
		}
		item = BG_FindItem( "Blue Flag" );
#ifdef IOQ3ZTM
		if ( !item || !itemRegistered[ ITEM_INDEX(item) ] )
#else
		if ( !item || !itemRegistered[ item - bg_itemlist ] )
#endif
		{
			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map\n" );
		}
		item = BG_FindItem( "Neutral Flag" );
#ifdef IOQ3ZTM
		if ( !item || !itemRegistered[ ITEM_INDEX(item) ] )
#else
		if ( !item || !itemRegistered[ item - bg_itemlist ] )
#endif
		{
			G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_neutralflag in map\n" );
		}
	}

	if( g_gametype.integer == GT_OBELISK ) {
		gentity_t	*ent;

		// check for the two obelisks
		ent = NULL;
		ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
		if( !ent ) {
			G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map\n" );
		}

		ent = NULL;
		ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
		if( !ent ) {
			G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map\n" );
		}
	}

#ifdef MISSIONPACK_HARVESTER
	if( g_gametype.integer == GT_HARVESTER ) {
		gentity_t	*ent;

		// check for all three obelisks
		ent = NULL;
		ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
		if( !ent ) {
			G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map\n" );
		}

		ent = NULL;
		ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
		if( !ent ) {
			G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map\n" );
		}

		ent = NULL;
		ent = G_Find( ent, FOFS(classname), "team_neutralobelisk" );
		if( !ent ) {
			G_Printf( S_COLOR_YELLOW "WARNING: No team_neutralobelisk in map\n" );
		}
	}
#endif
#endif
}

/*
==============
ClearRegisteredItems
==============
*/
void ClearRegisteredItems( void ) {
	memset( itemRegistered, 0, sizeof( itemRegistered ) );

	// players always start with the base weapon
#ifdef TURTLEARENA // HOLDABLE
	// Start with shurikens
#ifdef IOQ3ZTM // LASERTAG
	if (!g_laserTag.integer)
#endif
	RegisterItem( BG_FindItemForHoldable( HI_SHURIKEN ) );
#endif
#ifndef TA_WEAPSYS
#ifdef IOQ3ZTM // LASERTAG
	if (g_laserTag.integer)
		RegisterItem( BG_FindItemForWeapon( WP_RAILGUN ) );
	else
	{
#endif
	RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
	RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
#ifdef IOQ3ZTM // LASERTAG
	}
#endif
#endif
#ifdef MISSIONPACK_HARVESTER
	if( g_gametype.integer == GT_HARVESTER ) {
		RegisterItem( BG_FindItem( "Red Cube" ) );
		RegisterItem( BG_FindItem( "Blue Cube" ) );
	}
#endif
}

/*
===============
RegisterItem

The item will be added to the precache list
===============
*/
void RegisterItem( gitem_t *item ) {
#ifdef TA_WEAPSYS
	int itemNum;
#endif
	if ( !item ) {
#ifdef TA_ITEMSYS
		return;
#else
		G_Error( "RegisterItem: NULL" );
#endif
	}
#ifdef TA_WEAPSYS
	itemNum = ITEM_INDEX(item);
	if (itemNum < 0 || itemNum >= MAX_ITEMS)
	{
		G_Error( "RegisterItem: itemNum %d out of range [0-%d]\n", itemNum, MAX_ITEMS-1);
		return;
	}
	itemRegistered[ itemNum ] = qtrue;
#else
	itemRegistered[ ITEM_INDEX(item) ] = qtrue;
#endif
}


/*
===============
SaveRegisteredItems

Write the needed items to a config string
so the client will know which ones to precache
===============
*/
void SaveRegisteredItems( void ) {
	char	string[MAX_ITEMS+1];
	int		i;
	int		count;

	count = 0;
#ifdef TA_ITEMSYS
	for ( i = 0 ; i < BG_NumItems() ; i++ )
#else
	for ( i = 0 ; i < bg_numItems ; i++ )
#endif
	{
		if ( itemRegistered[i] ) {
			count++;
			string[i] = '1';
		} else {
			string[i] = '0';
		}
	}
#ifdef TA_ITEMSYS
	string[ BG_NumItems() ] = 0;
#else
	string[ bg_numItems ] = 0;
#endif

	G_DPrintf( "%i items registered\n", count );
	trap_SetConfigstring(CS_ITEMS, string);
}

/*
============
G_ItemDisabled
============
*/
int G_ItemDisabled( gitem_t *item ) {

	char name[128];

	Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
	return trap_Cvar_VariableIntegerValue( name );
}

/*
============
G_SpawnItem

Sets the clipping size and plants the object on the floor.

Items can't be immediately dropped to floor, because they might
be on an entity that hasn't spawned yet.
============
*/
void G_SpawnItem (gentity_t *ent, gitem_t *item) {
#ifdef IOQ3ZTM // RENDERFLAGS
	int mirrorType;
#endif

	G_SpawnFloat( "random", "0", &ent->random );
	G_SpawnFloat( "wait", "0", &ent->wait );
#ifdef IOQ3ZTM // RENDERFLAGS
	G_SpawnInt("mirrorType", "0", &mirrorType );

	if (mirrorType == 1)
		ent->s.eFlags |= EF_ONLY_MIRROR;
	else if (mirrorType == 2)
		ent->s.eFlags |= EF_NOT_MIRROR;
#endif

	RegisterItem( item );
	if ( G_ItemDisabled(item) )
		return;

	ent->item = item;
	// some movers spawn on the second frame, so delay item
	// spawns until the third frame so they can ride trains
	ent->nextthink = level.time + FRAMETIME * 2;
	ent->think = FinishSpawningItem;

	ent->physicsBounce = 0.50;		// items are bouncy

	if ( item->giType == IT_POWERUP ) {
		G_SoundIndex( "sound/items/poweruprespawn.wav" );
		G_SpawnFloat( "noglobalsound", "0", &ent->speed);
	}

#ifdef MISSIONPACK
	if ( item->giType == IT_PERSISTANT_POWERUP ) {
		ent->s.generic1 = ent->spawnflags;
	}
#endif
}

#ifdef TA_WEAPSYS
/*
============
G_RandomWeaponItem
============
*/
gitem_t *G_RandomWeaponItem( gentity_t *ent, int flags ) {
	int validWeapons[MAX_BG_WEAPON_GROUPS];
	int numweapons;
	int i;

	numweapons = 0;

	for (i = 1; i < BG_NumWeaponGroups(); i++)
	{
		if (!bg_weapongroupinfo[i].item->classname[0]) {
			continue;
		}
		if (!bg_weapongroupinfo[i].randomSpawn) {
			continue;
		}
		if (!(flags & 1)) // Don't spawn melee weapons
		{
			if (BG_WeaponHasMelee(i)) {
				continue;
			}
		}
		if (!(flags & 2)) // Don't spawn guns
		{
			if (BG_WeaponHasType(i, WT_GUN)) {
				continue;
			}
		}
		if ( G_ItemDisabled(bg_weapongroupinfo[i].item) ) {
			continue;
		}

		validWeapons[numweapons++] = i;
		// Hmm... I have to cache all of the weapons that it may spawn...
		RegisterItem( bg_weapongroupinfo[i].item );
		//Com_Printf("DEBUG: G_RandomWeaponItem: %s\n", bg_weapongroupinfo[i].item.classname);
	}

	if (!numweapons) {
		Com_Printf("G_RandomWeaponItem: NULL\n");
		return NULL;
	}

	// Random weapon item
	return bg_weapongroupinfo[validWeapons[rand() % numweapons]].item;
}

/*QUAKED weapon_random (1 0 0) (-16 -16 -16) (16 16 16) SUPSPENDED MELEE GUNS CONSTANT
Spawns a random weapon.
"wait" Respawn Delay
"random" Respawn Delay Variance
*/
void SP_weapon_random( gentity_t *ent ) {
	gitem_t *item;
#ifdef IOQ3ZTM // RENDERFLAGS
	int mirrorType;
#endif

	// Default to melee and guns
	if (!(ent->spawnflags & 2) && !(ent->spawnflags & 4)) {
		ent->spawnflags |= (2|4);
	}

	// No not constant random weapon...
	if (!(ent->spawnflags & 8))
	{
		// Change weapons on respawn
		ent->s.eFlags |= EF_VOTED;
	}


	G_SpawnFloat( "random", "0", &ent->random );
	G_SpawnFloat( "wait", "0", &ent->wait );
#ifdef IOQ3ZTM // RENDERFLAGS
	G_SpawnInt("mirrorType", "0", &mirrorType );

	if (mirrorType == 1)
		ent->s.eFlags |= EF_ONLY_MIRROR;
	else if (mirrorType == 2)
		ent->s.eFlags |= EF_NOT_MIRROR;
#endif

	item = G_RandomWeaponItem(ent, ent->spawnflags>>1);
	if (!item) {
		return;
	}

	ent->item = item;
	// some movers spawn on the second frame, so delay item
	// spawns until the third frame so they can ride trains
	ent->nextthink = level.time + FRAMETIME * 2;
	ent->think = FinishSpawningItem;

	ent->physicsBounce = 0.50;		// items are bouncy
}
#endif

/*
================
G_BounceItem

================
*/
void G_BounceItem( gentity_t *ent, trace_t *trace ) {
	vec3_t	velocity;
	float	dot;
	int		hitTime;

	// reflect the velocity on the trace plane
	hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
	BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
	dot = DotProduct( velocity, trace->plane.normal );
	VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );

	// cut the velocity to keep from bouncing forever
	VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );

	// check for stop
	if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
		trace->endpos[2] += 1.0;	// make sure it is off ground
		SnapVector( trace->endpos );
		G_SetOrigin( ent, trace->endpos );
		ent->s.groundEntityNum = trace->entityNum;
		return;
	}

	VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
	VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
	ent->s.pos.trTime = level.time;
}


/*
================
G_RunItem

================
*/
void G_RunItem( gentity_t *ent ) {
	vec3_t		origin;
	trace_t		tr;
	int			contents;
	int			mask;

#ifdef IOQ3ZTM // DROP_ITEM_FIX
	// If the dropped item wasn't yet outside the player body
	//  AND it was waited the delay time
	if (ent->item && ent->item->giType != IT_BAD
		&& (ent->flags & FL_DROPPED_ITEM) && ent->s.generic1 > 0
		&& ent->s.time2 < level.time)
	{
		// check if the prox mine is outside the owner bbox
		trap_Trace( &tr, ent->r.currentOrigin, ent->s.mins, ent->s.maxs, ent->r.currentOrigin, ENTITYNUM_NONE, ent->clipmask );
		if (!tr.startsolid || tr.entityNum != ent->s.generic1-1/*ent->r.ownerNum*/) {
			ent->s.generic1 = 0;
			//G_Printf("DEBUG: Dropped item is not inside owner player!\n");
		}
	}
#endif

	// if groundentity has been set to -1, it may have been pushed off an edge
	if ( ent->s.groundEntityNum == -1 ) {
		if ( ent->s.pos.trType != TR_GRAVITY ) {
			ent->s.pos.trType = TR_GRAVITY;
			ent->s.pos.trTime = level.time;
		}
	}

	if ( ent->s.pos.trType == TR_STATIONARY ) {
		// check think function
		G_RunThink( ent );
		return;
	}

	// get current position
	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

	// trace a line from the previous position to the current position
	if ( ent->clipmask ) {
		mask = ent->clipmask;
	} else {
#ifdef TURTLEARENA // NO_BODY_TRACE
		mask = MASK_PLAYERSOLID;//MASK_SOLID;
#else
		mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
#endif
	}
	trap_Trace( &tr, ent->r.currentOrigin, ent->s.mins, ent->s.maxs, origin, 
		ent->r.ownerNum, mask );

	VectorCopy( tr.endpos, ent->r.currentOrigin );

	if ( tr.startsolid ) {
		tr.fraction = 0;
	}

	trap_LinkEntity( ent );	// FIXME: avoid this for stationary?

	// check think function
	G_RunThink( ent );

	if ( tr.fraction == 1 ) {
		return;
	}

	// if it is in a nodrop volume, remove it
	contents = trap_PointContents( ent->r.currentOrigin, -1 );
	if ( contents & CONTENTS_NODROP ) {
		if (ent->item && ent->item->giType == IT_TEAM) {
			Team_FreeEntity(ent);
		} else {
			G_FreeEntity( ent );
		}
		return;
	}

	G_BounceItem( ent, &tr );
}

