/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program 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.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */
#include "g_local.h"

#define	    ARMOR_SHARD_VALUE	5

gitem_armor_t g_armor_info  = { 50, 100, 0.66f, ARMOR_GA };
gitem_armor_t y_armor_info  = { 100, 150, 0.66f, ARMOR_YA };
gitem_armor_t r_armor_info  = { 150, 200, 0.66f, ARMOR_RA }; // has armor decay

#define HEALTH_IGNORE_MAX   1
#define HEALTH_TIMED	    2

#define SHELL_TIMEOUT	30000
#define QUAD_TIMEOUT	30000

static void Use_Quad( edict_t *ent, gitem_t *item );
static int quad_drop_timeout_hack;
static int shell_drop_timeout_hack;

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

void DoRespawn( edict_t *ent )
{
	if( ent->team )
	{
		edict_t	*master;
		int count;
		int choice;

		master = ent->teammaster;

		//in ctf, when we are weapons stay, only the master of a team of weapons is spawned
		if( game.gametype == GAMETYPE_CTF &&
		   ( dmflags->integer & DF_WEAPONS_STAY ) &&
		   master->item && ( master->item->type & IT_WEAPON ) )
			ent = master;
		else
		{
			for( count = 0, ent = master; ent; ent = ent->chain, count++ ) ;

			choice = rand() % count;

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

	ent->r.svflags &= ~SVF_NOCLIENT;
	ent->r.solid = SOLID_TRIGGER;
	if( trap_Cvar_Get( "sv_skilllevel", "0", CVAR_SERVERINFO )->integer == 0 )
	{
		//ent->s.effects |= EF_RESPAWN_INDICATOR;
		//TODO: add 2 bits to respawn indicator for indication ;)
		ent->s.effects &= ~EF_RESPAWN_INDICATOR;
	}
	else
	{
		ent->s.effects &= ~EF_RESPAWN_INDICATOR;
	}
	GClip_LinkEntity( ent );

	// send an effect
	G_AddEvent( ent, EV_ITEM_RESPAWN, ent->item ? ent->item->tag : 0, qtrue );
}

void SetRespawn( edict_t *ent, float delay )
{
	if( delay < 0 )
	{
		G_FreeEdict( ent );
		return;
	}

	// oki... respawn indicators. i dont turn on SVF_NOCLIENT
	// and send an event...

#if 0
	if( trap_Cvar_Get( "sv_skilllevel", "0", CVAR_SERVERINFO )->integer == 0 )
	{
		ent->s.effects |= EF_RESPAWN_INDICATOR;
	}
	else
#endif
	{
		ent->r.svflags |= SVF_NOCLIENT;
	}

	ent->flags |= FL_RESPAWN;
	ent->r.solid = SOLID_NOT;
	ent->nextthink = level.time + 1000 * delay;
	ent->think = DoRespawn;
	GClip_LinkEntity( ent );
}


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

static qboolean Pickup_Powerup( edict_t *ent, edict_t *other )
{
	int quantity;

	if( !ent->item || !ent->item->tag )
		return qfalse;

	quantity = other->r.client->inventory[ent->item->tag];

	//if ((game.gametype == GAMETYPE_COOP) && (ent->item->flags & ITFLAG_STAY_COOP) && (quantity > 0))
	//	return qfalse;

	other->r.client->inventory[ent->item->tag]++;

	if( !G_Gametype_CanRespawnItem( ent->item ) )
		return qtrue;

	//set for respawn if in multiplayer mode
	if( !( ent->spawnflags & DROPPED_ITEM ) )
		SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );

	if( ( dmflags->integer & DF_INSTANT_ITEMS )
	   || ( ( ent->item->tag == POWERUP_QUAD ) && ( ent->spawnflags & DROPPED_PLAYER_ITEM ) )
	   || ( ( ent->item->tag == POWERUP_SHELL ) && ( ent->spawnflags & DROPPED_PLAYER_ITEM ) ) )
	{
		if( ( ent->item->tag == POWERUP_QUAD ) && ( ent->spawnflags & DROPPED_PLAYER_ITEM ) )
			quad_drop_timeout_hack = ( ent->nextthink - level.time );

		if( ( ent->item->tag == POWERUP_SHELL ) && ( ent->spawnflags & DROPPED_PLAYER_ITEM ) )
			shell_drop_timeout_hack = ( ent->nextthink - level.time );

		G_UseItem( other, ent->item );
	}

	return qtrue;
}

static void Drop_General( edict_t *ent, gitem_t *item )
{
	if( Drop_Item( ent, item ) )
	{
		ent->r.client->inventory[item->tag]--;
		ValidateSelectedItem( ent );
	}
}


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

static qboolean Pickup_AmmoPack( edict_t *ent, edict_t *other )
{
	gclient_t *client = other->r.client;
	firedef_t *firedef;
	int i;

	if( !client )
		return qfalse;

	for( i = 1; i < AMMO_TOTAL; i++ )
	{
		firedef = GS_FiredefForAmmo( i );
		if( firedef )
		{
			client->inventory[i] += ent->invpak[i];
			if( client->inventory[i] > firedef->ammo_max )
				client->inventory[i] = firedef->ammo_max;
		}
	}

	if( !( ent->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( ent->item ) )
		SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );

	return qtrue;
}

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

void Use_Quad( edict_t *ent, gitem_t *item )
{
	int timeout;

	ent->r.client->inventory[item->tag]--;
	ValidateSelectedItem( ent );

	if( quad_drop_timeout_hack )
	{
		timeout = quad_drop_timeout_hack;
		quad_drop_timeout_hack = 0;
	}
	else
	{
		timeout = QUAD_TIMEOUT; //30000;
	}

	if( ent->r.client->quad_timeout > level.time )
		ent->r.client->quad_timeout += timeout;
	else
		ent->r.client->quad_timeout = level.time + timeout;

	//G_Sound( ent, CHAN_AUTO, trap_SoundIndex(S_QUAD_USE), 1, ATTN_NORM );
}

static void Use_Shell( edict_t *ent, gitem_t *item )
{
	int timeout;

	ent->r.client->inventory[item->tag]--;
	ValidateSelectedItem( ent );

	if( shell_drop_timeout_hack )
	{
		timeout = shell_drop_timeout_hack;
		shell_drop_timeout_hack = 0;
	}
	else
	{
		timeout = SHELL_TIMEOUT; //30000;
	}

	if( ent->r.client->shell_timeout > level.time )
		ent->r.client->shell_timeout += timeout;
	else
		ent->r.client->shell_timeout = level.time + timeout;

	//G_Sound( ent, CHAN_AUTO, trap_SoundIndex(S_SHELL_USE), 1, ATTN_NORM );
}

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

qboolean Add_Ammo( edict_t *ent, gitem_t *item, int count, qboolean add_it )
{
	firedef_t *firedef;
	int max;

	if( !ent->r.client )
		return qfalse;

	firedef = GS_FiredefForAmmo( item->tag );
	if( firedef )
		max = firedef->ammo_max;
	else
		max = -1;

	if( max < 0 )
		return qfalse;

	if( ent->r.client->inventory[item->tag] >= max )
		return qfalse;

	if( add_it )
	{
		ent->r.client->inventory[item->tag] += count;

		if( ent->r.client->inventory[item->tag] > max )
			ent->r.client->inventory[item->tag] = max;
	}

	return qtrue;
}

static qboolean Pickup_Ammo( edict_t *ent, edict_t *other )
{
	int oldcount;
	int count;
	qboolean weapon;

	weapon = ( ent->item->type & IT_WEAPON );

	if( ( weapon ) && ( dmflags->integer & DF_INFINITE_AMMO ) )
		count = 1000;
	else if( ent->count )
		count = ent->count;
	else
		count = ent->item->quantity;

	oldcount = other->r.client->inventory[ent->item->tag];

	if( !Add_Ammo( other, ent->item, count, qtrue ) )
		return qfalse;

	if( !( ent->spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) && G_Gametype_CanRespawnItem( ent->item ) )
		SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );

	return qtrue;
}

static void Drop_Ammo( edict_t *ent, gitem_t *item )
{
	edict_t	*dropped;
	int index;

	index = item->tag;
	dropped = Drop_Item( ent, item );
	if( dropped )
	{
		if( ent->r.client->inventory[index] >= item->quantity )
			dropped->count = item->quantity;
		else
			dropped->count = ent->r.client->inventory[index];

		ent->r.client->inventory[index] -= dropped->count;
	}
	ValidateSelectedItem( ent );
}


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

void MegaHealth_think( edict_t *self )
{
	if( self->r.owner->r.inuse && self->r.owner->s.team != TEAM_SPECTATOR &&
	    HEALTH_TO_INT( self->r.owner->health ) > self->r.owner->max_health )
	{
		self->nextthink = level.time + 1;
		//self->r.owner->health -= 1; // done in G_GameType_ClientHealthRule now
		return;
	}

	// player is back under max health so we can set respawn time for next MH
	if( !( self->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( self->item ) )
		SetRespawn( self, G_Gametype_RespawnTimeForItem( self->item ) );
	else
		G_FreeEdict( self );
}

static qboolean Pickup_Health( edict_t *ent, edict_t *other )
{
	if( !( ent->style & HEALTH_IGNORE_MAX ) )
		if( HEALTH_TO_INT( other->health ) >= other->max_health )
			return qfalse;

	// start from atleast 0.5, so the player sees his health increase the correct amount
	if( other->health < 0.5 )
		other->health = 0.5;

	other->health += ent->item->quantity;

	if( other->r.client )
		other->r.client->resp.health_taken += ent->item->quantity;

	if( !( ent->style & HEALTH_IGNORE_MAX ) )
	{
		if( other->health > other->max_health )
			other->health = other->max_health;
	}
	else
	{
		if( other->health > 200 )
			other->health = 200;
	}

	if( ent->style & HEALTH_TIMED )
	{
		ent->think = MegaHealth_think;
		ent->nextthink = level.time + 1;
		ent->r.owner = other;
		ent->flags |= FL_RESPAWN;
		ent->r.svflags |= SVF_NOCLIENT;
		ent->r.solid = SOLID_NOT;
	}
	else
	{
		if( !( ent->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( ent->item ) )
			SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );
	}

	G_AwardPlayerPickup( other, ent );

	return qtrue;
}

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

#define SALVAGE_AVERAGE
qboolean Add_Armor( edict_t *ent, edict_t *other, qboolean pick_it )
{
	gclient_t *client;
	int player_armor_tag;
	int item_armor_tag;
	gitem_armor_t *item_armor_info = NULL;
	int newarmortag = 0;
	int newcount = 0;

	if( !other->r.client )
		return qfalse;

	client = other->r.client;

	// organize our data
	player_armor_tag = client->armortag;
	item_armor_tag = ent->item->tag;
	item_armor_info = (gitem_armor_t *)ent->item->info;

	// if player has no armor, just use it
	if( !player_armor_tag )
	{
		// handle armor shards specially
		if( ent->item->tag == ARMOR_SHARD )
		{
			newarmortag = ARMOR_GA;
			newcount = ARMOR_SHARD_VALUE;
		}
		else
		{
			newarmortag = item_armor_info->armor;
			newcount = item_armor_info->base_count;
		}
	}
	//player had armor. Mix up.
	else
	{
		//find player armor infos
		gitem_armor_t *player_armor_info = (gitem_armor_t *)game.items[player_armor_tag]->info;
		int player_armor_count = client->armor;

		// handle armor shards specially
		if( ent->item->tag == ARMOR_SHARD )
		{
			//can't pick up shards if topped of RA
			if( player_armor_count >= ( (gitem_armor_t *)game.items[ARMOR_RA]->info )->max_count )
				return qfalse;

			newarmortag = player_armor_tag;
			newcount = min( player_armor_count + ARMOR_SHARD_VALUE, ( (gitem_armor_t *)game.items[ARMOR_RA]->info )->max_count );
		}
		// can't pick up if player has RA, and will pick up YA but isn't below YA's max count
		else if( player_armor_tag == ARMOR_RA
		         && player_armor_count >= y_armor_info.max_count
		         && ent->item->tag == ARMOR_YA )
		{
			return qfalse;
		}
		else if( player_armor_tag == ARMOR_GA && ent->item->tag == ARMOR_GA &&
		         player_armor_count >= ( (gitem_armor_t *)game.items[ARMOR_GA]->info )->max_count )
		{
			return qfalse;
		}
		// wsw: torbenh: this is considered BUG.
		//               however i leave this code here. but it should be removed, if the rules are
		//               pick all you can.
		//
		//		else if ( player_armor_tag != ARMOR_GA && ent->item->tag == ARMOR_GA )
		//		{
		//			return qfalse;
		//		}
		//player's armor is pickable
		else
		{
			float scale;

			scale = 1;
			newarmortag = item_armor_info->armor;
#ifdef SALVAGE_AVERAGE
			// convert the old armor quantity to it's quantity based in the new protection
			scale = player_armor_info->protection / item_armor_info->protection;
#endif //SALVAGE_AVERAGE

			newcount = item_armor_info->base_count + (int)( player_armor_count * scale );
			if( newcount > item_armor_info->max_count )
				newcount = item_armor_info->max_count;

			// do not pickup if newcount doesn't increase total protection
			if( (int)( newcount * item_armor_info->protection ) <= (int)( player_armor_count * player_armor_info->protection ) )
				return qfalse;
		}
	}

	// compute result
	if( !newarmortag || !newcount )
		return qfalse;

	if( pick_it )
	{
		//update player armor
		client->armor = newcount;
		client->armortag = newarmortag;
		// update armor taken stats
		if( ent->item->tag == ARMOR_SHARD )
			client->resp.armor_taken += ARMOR_SHARD_VALUE;
		else
			client->resp.armor_taken += item_armor_info->base_count;

		// picked. Set up for respawn
		if( !( ent->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( ent->item ) )  //newgametypes
			SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );
	}

	return qtrue;
}

static qboolean Pickup_Armor( edict_t *ent, edict_t *other )
{
	if( Add_Armor( ent, other, qtrue ) )
	{
		G_AwardPlayerPickup( other, ent );
		return qtrue;
	}
	else
	{
		return qfalse;
	}
}

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

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

//===============
//Touch_Item
//===============
void Touch_Item( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	qboolean taken;

	if( !other->r.client )
		return;
	if( G_IsDead( other ) )
		return; // dead people can't pickup
	if( !ent->item )
		return;

	if( !( ent->item->flags & ITFLAG_PICKABLE ) )
		return; // not a grabbable item

	if( !G_Gametype_CanPickUpItem( ent->item ) )
		return;

	taken = G_PickupItem( ent, other );
	if( taken )
	{
		// flash the screen
		G_AddPlayerStateEvent( other->r.client, PSEV_PICKUP, ( ent->item->flags & IT_WEAPON ? ent->item->tag : 0 ) );

		// for messages
		other->r.client->resp.last_pickup = ent;

		// show icon and name on status bar
		other->r.client->ps.stats[STAT_PICKUP_ITEM] = ent->item->tag;
		other->r.client->pickup_msg_time = level.time + 3000;

		if( ent->item->pickup_sound )
		{
			if( ent->item->type & IT_POWERUP )
				G_Sound( other, CHAN_ITEM, trap_SoundIndex( ent->item->pickup_sound ), 1, ATTN_NORM );
			else
				G_Sound( other, CHAN_AUTO, trap_SoundIndex( ent->item->pickup_sound ), 1, ATTN_NORM );
		}
	}

	if( !( ent->spawnflags & ITEM_TARGETS_USED ) )
	{
		G_UseTargets( ent, other );
		ent->spawnflags |= ITEM_TARGETS_USED;
	}

	if( !taken )
		return;

	// wsw: Medar: I don't really know what this does tbh :P
	if( ( ent->spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) )
	{
		if( ent->flags & FL_RESPAWN )
			ent->flags &= ~FL_RESPAWN;
		else
			G_FreeEdict( ent );
	}
}

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

static void drop_temp_touch( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	if( other == ent->r.owner )
		return;
	Touch_Item( ent, other, plane, surfFlags );
}

static void drop_make_touchable( edict_t *ent )
{
	int timeout;
	ent->touch = Touch_Item;
	timeout = G_Gametype_DroppedItemTimeout( ent->item );
	if( timeout )
	{
		ent->nextthink = level.time + 1000 * timeout;
		ent->think = G_FreeEdict;
	}
}

edict_t *Drop_Item( edict_t *ent, gitem_t *item )
{
	edict_t	*dropped;
	vec3_t forward, right;
	vec3_t offset;

	if( !G_Gametype_CanDropItem( item, qfalse ) )
		return NULL;

	dropped = G_Spawn();
	dropped->classname = item->classname;
	dropped->item = item;
	dropped->spawnflags = DROPPED_ITEM;
	VectorCopy( item_box_mins, dropped->r.mins );
	VectorCopy( item_box_maxs, dropped->r.maxs );
	dropped->r.solid = SOLID_TRIGGER;
	dropped->movetype = MOVETYPE_TOSS;
	dropped->touch = drop_temp_touch;
	dropped->r.owner = ent;
	dropped->r.svflags &= ~SVF_NOCLIENT;
	dropped->s.team = ent->s.team; // wsw : jal : newgametypes
	dropped->s.type = ET_ITEM;      // wsw : jal : simpleitems
	dropped->s.skinnum = item->tag;
	dropped->s.effects = 0; // default effects are applied client side
	dropped->s.renderfx = 0;
	dropped->s.modelindex = trap_ModelIndex( dropped->item->world_model[0] );
	dropped->s.modelindex2 = trap_ModelIndex( dropped->item->world_model[1] );

	if( ent->r.client )
	{
		trace_t	trace;

		AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
		VectorSet( offset, 24, 0, -16 );
		G_ProjectSource( ent->s.origin, offset, forward, right, dropped->s.origin );
		G_Trace( &trace, ent->s.origin, dropped->r.mins, dropped->r.maxs,
		         dropped->s.origin, ent, CONTENTS_SOLID );
		VectorCopy( trace.endpos, dropped->s.origin );
	}
	else
	{
		AngleVectors( ent->s.angles, forward, right, NULL );
		VectorCopy( ent->s.origin, dropped->s.origin );
	}

	VectorScale( forward, 100, dropped->velocity );
	dropped->velocity[2] = 300;

	dropped->think = drop_make_touchable;
	dropped->nextthink = level.time + 1000;

	ent->r.client->resp.last_drop_item = item;
	VectorCopy( dropped->s.origin, ent->r.client->resp.last_drop_location );

	GClip_LinkEntity( dropped );

	return dropped;
}

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

//================
//G_PickupItem
//================
qboolean G_PickupItem( edict_t *ent, edict_t *other )
{
	gitem_t	*it;

	if( !ent || !other )
		return qfalse;

	if( other->r.client && other->s.team == TEAM_SPECTATOR )
		return qfalse;

	if( !ent->item || !( ent->item->flags & ITFLAG_PICKABLE ) )
		return qfalse;

	it = ent->item;

	if( it->type & IT_WEAPON )
	{
		return Pickup_Weapon( ent, other );
	}
	else if( it->type & IT_AMMO )
	{
		if( !Q_stricmp( it->classname, "item_ammopack" ) )
			return Pickup_AmmoPack( ent, other );
		else
			return Pickup_Ammo( ent, other );
	}
	else if( it->type & IT_ARMOR )
	{
		return Pickup_Armor( ent, other );
	}
	else if( it->type & IT_HEALTH )
	{
		return Pickup_Health( ent, other );
	}
	else if( it->type & IT_POWERUP )
	{
		return Pickup_Powerup( ent, other );
	}
	else if( it->type & IT_FLAG )
	{
		return G_Gametype_CTF_Pickup_Flag( ent, other );
	}

	return qfalse;
}

//================
//G_DropItem
//================
void G_DropItem( edict_t *ent, gitem_t *it )
{
	if( !it || !( it->flags & ITFLAG_DROPABLE ) )
		return;

	if( !G_Gametype_CanDropItem( it, qfalse ) )
		return;

	if( it->type & IT_WEAPON )
	{
		Drop_Weapon( ent, it );
	}
	else if( it->type & IT_AMMO )
	{
		Drop_Ammo( ent, it );
	}
	else if( it->type & IT_FLAG )
	{
		G_Gametype_CTF_Drop_Flag( ent, it );
	}
	else
	{
		Drop_General( ent, it );
	}
}

//================
//G_UseItem
//================
void G_UseItem( edict_t *ent, gitem_t *it )
{
	if( !it || !( it->flags & ITFLAG_USABLE ) )
		return;

	if( it->type & IT_WEAPON )
	{
		Use_Weapon( ent, it );
	}
	else if( it->type & IT_POWERUP )
	{
		if( it->tag == POWERUP_QUAD )
			Use_Quad( ent, it );
		else if( it->tag == POWERUP_SHELL )
			Use_Shell( ent, it );
	}
}

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


//================
//Finish_SpawningItem
//================
static void Finish_SpawningItem( edict_t *ent )
{
	trace_t	tr;
	vec3_t dest;

	VectorCopy( item_box_mins, ent->r.mins );
	VectorCopy( item_box_maxs, ent->r.maxs );

	if( ent->model )
	{
		ent->s.modelindex = trap_ModelIndex( ent->model );
	}
	else
	{
		if( ent->item->world_model[0] )
			ent->s.modelindex = trap_ModelIndex( ent->item->world_model[0] );
		if( ent->item->world_model[1] )
			ent->s.modelindex2 = trap_ModelIndex( ent->item->world_model[1] );
	}

	ent->s.renderfx = 0;
	ent->r.solid = SOLID_TRIGGER;
	ent->r.svflags &= ~SVF_NOCLIENT;
	ent->movetype = MOVETYPE_TOSS;
	ent->touch = Touch_Item;

	if( !( ent->spawnflags & 1 ) )
	{                           // droptofloor
		// see if they start in a solid position
		G_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_SOLID );
		if( tr.startsolid )
		{
			// move it 1 unit up, cause it's typical they share the leaf with the floor
			VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + 1 );
			G_Trace( &tr, dest, ent->r.mins, ent->r.maxs, ent->s.origin, ent, MASK_SOLID );
			if( tr.startsolid )
			{
				G_Printf( "Warning: %s %s spawns inside solid. Inhibited\n", ent->classname, vtos( ent->s.origin ) );
				G_FreeEdict( ent );
				return;
			}
			VectorCopy( dest, ent->s.origin );
		}


		VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 128 );
		G_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent, MASK_SOLID );
		if( tr.startsolid )
		{
			G_Printf( "Warning: %s %s spawns inside solid. Inhibited\n", ent->classname, vtos( ent->s.origin ) );
			G_FreeEdict( ent );
			return;
		}

		VectorCopy( tr.endpos, ent->s.origin );
	}
	else
	{
		ent->gravity = 0;
	}

	if( ent->team )
	{
		ent->flags &= ~FL_TEAMSLAVE;
		ent->chain = ent->teamchain;
		ent->teamchain = NULL;

		ent->r.svflags |= SVF_NOCLIENT;
		ent->r.solid = SOLID_NOT;

		// team slaves and targeted items aren't present at start
		if( ent == ent->teammaster && !ent->targetname )
		{
			ent->nextthink = level.time + 1;
			ent->think = DoRespawn;
			GClip_LinkEntity( ent );
		}
	}
	else if( ent->targetname )
	{
		ent->r.svflags |= SVF_NOCLIENT;
		ent->r.solid = SOLID_NOT;
	}
}


//===============
//PrecacheItem
//
//Precaches all data needed for a given item.
//This will be called for each item spawned in a level,
//and for each item in each client's inventory.
//===============
void PrecacheItem( gitem_t *it )
{
	int i;
	char *s, *start;
	char data[MAX_QPATH];
	int len;
	gitem_t	*ammo;

	if( !it )
		return;

	if( it->pickup_sound )
		trap_SoundIndex( it->pickup_sound );
	for( i = 0; i < MAX_ITEM_MODELS; i++ )
	{
		if( it->world_model[i] )
			trap_ModelIndex( it->world_model[i] );
	}

	if( it->icon )
		trap_ImageIndex( it->icon );

	// parse everything for its ammo
	if( it->ammo_tag )
	{
		ammo = game.items[it->ammo_tag];
		if( ammo != it )
			PrecacheItem( ammo );
	}

	// parse the space separated precache string for other items
	for( i = 0; i < 3; i++ )
	{
		if( i == 0 )
			s = it->precache_models;
		else if( i == 1 )
			s = it->precache_sounds;
		else
			s = it->precache_images;

		if( !s || !s[0] )
			continue;

		while( *s )
		{
			start = s;
			while( *s && *s != ' ' )
				s++;

			len = s-start;
			if( len >= MAX_QPATH || len < 5 )
				G_Error( "PrecacheItem: %s has bad precache string", it->classname );
			memcpy( data, start, len );
			data[len] = 0;
			if( *s )
				s++;

			if( i == 0 )
				trap_ModelIndex( data );
			else if( i == 1 )
				trap_SoundIndex( data );
			else
				trap_ImageIndex( data );
		}
	}
}

//============
//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 SpawnItem( edict_t *ent, gitem_t *item )
{
	// wsw : jal : set items as ET_ITEM for simpleitems
	ent->s.type = ET_ITEM;
	ent->s.skinnum = item->tag;
	ent->item = item;
	ent->s.effects = 0; // default effects are applied client side

	if( item->type & IT_HEALTH )
	{
		if( item->tag == HEALTH_SMALL )
			ent->style = HEALTH_IGNORE_MAX;
		else if( item->tag == HEALTH_MEGA )
			ent->style = HEALTH_IGNORE_MAX|HEALTH_TIMED;
	}

	ent->think = Finish_SpawningItem;
	ent->nextthink = level.time + 2 * game.frametime; // items start after other solids

	// flags are server animated and have special handling
	if( item->type & IT_FLAG && game.gametype == GAMETYPE_CTF )
	{
		ent->think = G_Gametype_CTF_FlagSetup;
	}
}

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

//===============
//InitItems
//
//Validate item tags and sort by tag into game.items list
//===============
void InitItems( void )
{
	int tag, count;
	gitem_t	*item;
	int num_items;

	// count items
	for( num_items = 1, item = &itemdefs[1]; item->classname; item++, num_items++ )
	{
	}

	//clear
	for( tag = 0; tag < MAX_ITEMS; tag++ )  //zero will always be NULL
		game.items[tag] = NULL;

	// store item with tags in the array at position of tag value
	for( tag = 1, count = 0; tag < num_items; tag++ )
	{
		game.items[tag] = NULL;
		for( item = &itemdefs[1]; item->classname; item++ )
		{
			if( item->tag == tag )
			{

				// found the same tag value twice
				if( game.items[tag] != NULL )
					G_Error( "InitItems: Found two different items with the same tag value: %s -> %s ", game.items[tag]->classname, item->classname );

				game.items[tag] = item;
				count++;
				//break;
			}
		}
	}

	//find the first with tag being zero
	for( item = &itemdefs[1]; item->classname; item++ )
	{
		if( !item->tag )
			break;
	}

	//asign tag values to items without tags
	while( item->classname )
	{
		for( item = &itemdefs[1]; item->classname; item++ )
		{
			if( !item->tag )
			{
				//find a free spot for it
				for( tag = 1; tag < MAX_ITEMS; tag++ )
				{
					if( !game.items[tag] )
						break;
				}
				if( tag == MAX_ITEMS )
					G_Error( "InitItems: Couldn't find a free spot into game.items array for %s\n", item->classname );

				item->tag = tag;
				game.items[tag] = item; //put it on the list

				if( developer->value )
					Com_Printf( "WARNING: InitItems found item '%s' without a tag value. Assigned: %i\n", item->classname, tag );

				count++;
				break;
			}
		}
	}

	game.numItems = count + 1; //item zero was not counted
	Com_Printf( "Initialized %i Items\n", count );

	// Init armor items
	game.items[ARMOR_GA]->info = (gitem_armor_t *)&g_armor_info;
	game.items[ARMOR_YA]->info = (gitem_armor_t *)&y_armor_info;
	game.items[ARMOR_RA]->info = (gitem_armor_t *)&r_armor_info;

	// Init weapon items
	GS_InitWeapons();
}

//===============
//SetItemNames
//
//Called by worldspawn
//===============
void SetItemNames( void )
{
	int i;

	for( i = 0; i < game.numItems; i++ )
	{
		if( game.items[i] )
			trap_ConfigString( CS_ITEMS+i, game.items[i]->pickup_name );
	}
}

int G_ItemRespawnTimer( char *class_name )
{
	edict_t *item;

	if( !class_name )
		return 0;

	item = G_Find( 0, FOFS( classname ), class_name );

	if( !item )
		return 0;

	if( item->r.inuse && !item->r.client && item->nextthink >= level.time && item->flags & FL_RESPAWN )
	{
		int i = item->nextthink - level.time;
		if( i == 1 && !Q_stricmp( class_name, "item_health_mega" ) )  // mega has different behaviour
			return 254;
		else
			return (int) ( (float)( i ) * 0.001f ) + 1;
	}
	else return 0;
}
