// RAVEN BEGIN
// bdube: note that this file is no longer merged with Doom3 updates
//
// MERGE_DATE 09/30/2004

#include "../idlib/precompiled.h"
#pragma hdrstop

#include "Game_local.h"


/*
===============================================================================

  idItem

===============================================================================
*/

const idEventDef EV_DropToFloor( "<dropToFloor>" );
const idEventDef EV_RespawnItem( "respawn" );
const idEventDef EV_RespawnFx( "<respawnFx>" );
const idEventDef EV_GetPlayerPos( "<getplayerpos>" );
const idEventDef EV_HideObjective( "<hideobjective>", "e" );
const idEventDef EV_CamShot( "<camshot>" );

// RAVEN BEGIN
// abahr:
const idEventDef EV_SetGravity( "<setGravity>" );
// RAVEN END

CLASS_DECLARATION( idEntity, idItem )
	EVENT( EV_DropToFloor,		idItem::Event_DropToFloor )
	EVENT( EV_Touch,			idItem::Event_Touch )
	EVENT( EV_Activate,			idItem::Event_Trigger )
	EVENT( EV_RespawnItem,		idItem::Event_Respawn )
	EVENT( EV_RespawnFx,		idItem::Event_RespawnFx )
// RAVEN BEGIN
// abahr
	EVENT( EV_SetGravity,		idItem::Event_SetGravity )
// RAVEN END

END_CLASS


/*
================
idItem::idItem
================
*/
idItem::idItem() {
	spin = false;
	inView = false;
	skin = NULL;
	pickupSkin = NULL;
	inViewTime = 0;
	lastCycle = 0;
	lastRenderViewTime = -1;
	itemShellHandle = -1;
	shellMaterial = NULL;
	orgOrigin.Zero();
	canPickUp = true;
	fl.networkSync = true;
	trigger = NULL;
	syncPhysics = false;
	srvReady = -1;
	clReady = -1;
	effectIdle = NULL;
	itemPVSArea = 0;
	effectIdle = NULL;
	simpleItem = false;
	pickedUp = false;
}

/*
================
idItem::~idItem
================
*/
idItem::~idItem() {
	// remove the highlight shell
	if ( itemShellHandle != -1 ) {
		gameRenderWorld->FreeEntityDef( itemShellHandle );
	}
	if ( trigger ) {
		delete trigger;
	}
	
	SetPhysics( NULL );
}

/*
================
idItem::Save
================
*/
void idItem::Save( idSaveGame *savefile ) const {
   	savefile->WriteClipModel( trigger );
	savefile->WriteBool( spin );

	savefile->WriteSkin( skin );
	savefile->WriteSkin( pickupSkin );

	savefile->WriteVec3( orgOrigin );

	savefile->WriteBool( pulse );
	savefile->WriteBool( canPickUp );

	savefile->WriteStaticObject( physicsObj );

//	savefile->WriteInt(itemShellHandle);	// cnicholson: Set at end of Restore, do not save
	savefile->WriteMaterial( shellMaterial );

	savefile->WriteBool( inView );
	savefile->WriteInt( inViewTime );
	savefile->WriteInt( lastCycle );
	savefile->WriteInt( lastRenderViewTime );
}

/*
================
idItem::Restore
================
*/
void idItem::Restore( idRestoreGame *savefile ) {
	savefile->ReadClipModel( trigger );
	savefile->ReadBool( spin );

	savefile->ReadSkin( skin );
	savefile->ReadSkin( pickupSkin );

	savefile->ReadVec3( orgOrigin );

	savefile->ReadBool( pulse );
	savefile->ReadBool( canPickUp );

	savefile->ReadStaticObject ( physicsObj );

//	savefile->ReadInt(itemShellHandle);	// cnicholson: Set at end of function, do not restore
	savefile->ReadMaterial( shellMaterial );

	savefile->ReadBool( inView );
	savefile->ReadInt( inViewTime );
	savefile->ReadInt( lastCycle );
	savefile->ReadInt( lastRenderViewTime );

	RestorePhysics( &physicsObj );
	
	physicsObj.SetSelf( this );
	
	itemShellHandle = -1;
}

/*
================
idItem::UpdateRenderEntity
================
*/
bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const {
	if( simpleItem ) {
		return false;
	}

	if ( lastRenderViewTime == renderView->time ) {
		return false;
	}

	lastRenderViewTime = renderView->time;

	// check for glow highlighting if near the center of the view
	idVec3 dir = renderEntity->origin - renderView->vieworg;
	dir.Normalize();
	float d = dir * renderView->viewaxis[0];

	// two second pulse cycle
	float cycle = ( renderView->time - inViewTime ) / 2000.0f;

	if ( d > 0.94f ) {
		if ( !inView ) {
			inView = true;
			if ( cycle > lastCycle ) {
				// restart at the beginning
				inViewTime = renderView->time;
				cycle = 0.0f;
			}
		}
	} else {
		if ( inView ) {
			inView = false;
			lastCycle = ceil( cycle );
		}
	}

	// fade down after the last pulse finishes 
	if ( !inView && cycle > lastCycle ) {
		renderEntity->shaderParms[4] = 0.0f;
	} else {
		// pulse up in 1/4 second
		cycle -= (int)cycle;
		if ( cycle < 0.1f ) {
			renderEntity->shaderParms[4] = cycle * 10.0f;
		} else if ( cycle < 0.2f ) {
			renderEntity->shaderParms[4] = 1.0f;
		} else if ( cycle < 0.3f ) {
			renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f;
		} else {
			// stay off between pulses
			renderEntity->shaderParms[4] = 0.0f;
		}
	}

	// update every single time this is in view
	return true;
}

/*
================
idItem::UpdateTrigger
================
*/
void idItem::UpdateTrigger ( void ) {
	// update trigger position
// RAVEN BEGIN
// ddynerman: multiple clip worlds
	trigger->Link( this, 0, GetPhysics()->GetOrigin(), mat3_identity );
// RAVEN END
}

/*
================
idItem::ModelCallback
================
*/
bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) {
	const idItem *ent;

	// this may be triggered by a model trace or other non-view related source
	if ( !renderView ) {
		return false;
	}

	ent = static_cast<idItem *>(gameLocal.entities[ renderEntity->entityNum ]);
	if ( !ent ) {
		gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" );
	}

	return ent->UpdateRenderEntity( renderEntity, renderView );
}


/*
================
idItem::GetPhysicsToVisualTransform
================
*/
bool idItem::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
	if( simpleItem ) {
		if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetRenderView() ) {
			if( gameLocal.GetLocalPlayer()->spectating ) {
				idPlayer* spec = (idPlayer*)gameLocal.entities[ gameLocal.GetLocalPlayer()->spectator ];
				if( spec && spec->GetRenderView() ) {
					axis = spec->GetRenderView()->viewaxis;
				}
			} else {
				axis = gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis;	
			}
		} else {
			// dedicated server for instance
			axis = mat3_identity;
		}
		origin = idVec3( 0.0f, 0.0f, simpleItemScale / 2.0f );
		return true;
	}

	if( !spin || (gameLocal.isServer && !gameLocal.isListenServer) ) {
		return false;
	}

	idAngles ang;
	ang.pitch = ang.roll = 0.0f;
	ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f;
	axis = ang.ToMat3() * GetPhysics()->GetAxis();

	float scale = 0.005f;
	float offset = entityNumber * 0.685145f;		// rjohnson: just a random number here to shift the cos curve

	origin.Zero();

	origin += GetPhysics()->GetAxis()[2] * (4.0f + idMath::Cos( ( ( gameLocal.time + 1000 ) * scale ) + offset ) * 4.0f);

	return true;
}

// RAVEN BEGIN
// mekberg: added
/*
================
idItem::Collide
================
*/
bool idItem::Collide( const trace_t &collision, const idVec3 &velocity ) {
	idEntity* lol = gameLocal.entities[ collision.c.entityNum ];
	if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) {
		PostEventMS( &EV_Remove, 0 );
	}
	return false;
}
// RAVEN END

/*
================
idItem::Think
================
*/
void idItem::Think( void ) {
	if ( thinkFlags & TH_PHYSICS ) {
		RunPhysics();		
		UpdateTrigger();
	}

	if ( gameLocal.IsMultiplayer() && g_skipItemShadowsMP.GetBool() ) {
		renderEntity.suppressShadowInViewID = gameLocal.localClientNum + 1;
	} else {
		renderEntity.suppressShadowInViewID = 0;
	}

	if( !(simpleItem && pickedUp) ) {
		UpdateVisuals();
		Present();
	}
}

/*
================
idItem::Present
================
*/
void idItem::Present( void ) {
	idEntity::Present();

	if ( !fl.hidden && pulse ) {
		// also add a highlight shell model
		renderEntity_t	shell;

		shell = renderEntity;

		// we will mess with shader parms when the item is in view
		// to give the "item pulse" effect
		shell.callback = idItem::ModelCallback;
		shell.entityNum = entityNumber;
		shell.customShader = shellMaterial;
		if ( itemShellHandle == -1 ) {
			itemShellHandle = gameRenderWorld->AddEntityDef( &shell );
		} else {
			gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell );
		}
	}
}

/*
================
idItem::InstanceJoin
================
*/
void idItem::InstanceJoin( void ) {
	idEntity::InstanceJoin();

	UpdateModelTransform();
	if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) {
		PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true );
	}
}

/*
================
idItem::InstanceLeave
================
*/
void idItem::InstanceLeave( void ) {
	idEntity::InstanceLeave();

	StopEffect( "fx_idle", true );
}

/*
================
idItem::Spawn
================
*/
void idItem::Spawn( void ) {
	idStr		giveTo;
	idEntity *	ent;
	idVec3		vSize;
	idBounds	bounds(vec3_origin);

	// check for triggerbounds, which allows for non-square triggers (useful for, say, a CTF flag)	
	if ( spawnArgs.GetVector( "triggerbounds", "16 16 16", vSize )) {
		bounds.AddPoint(idVec3( vSize.x*0.5f,  vSize.y*0.5f, 0.0f));
		bounds.AddPoint(idVec3(-vSize.x*0.5f, -vSize.y*0.5f, vSize.z));
	}
	else {
		// create a square trigger for item pickup
		float tsize;
		spawnArgs.GetFloat( "triggersize", "16.0", tsize );
		bounds.ExpandSelf( tsize );
	}

// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_PUSH_HEAP_MEM(this);
// RAVEN END
	trigger = new idClipModel( idTraceModel( bounds ));
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_POP_HEAP();
// RAVEN END
// RAVEN BEGIN
// ddynerman: multiple clip worlds
	trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
// RAVEN END

	physicsObj.SetSelf ( this );
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_PUSH_HEAP_MEM(this);
// RAVEN END

	physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );

// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_POP_HEAP();
// RAVEN END
	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
	physicsObj.SetAxis( GetPhysics()->GetAxis() );
	physicsObj.SetGravity( gameLocal.GetGravity() );
	physicsObj.SetContents( 0 );
	physicsObj.SetClipMask( MASK_SOLID );
	physicsObj.SetFriction( 0.0f, 0.0f, 6.0f );
	SetPhysics( &physicsObj );

	if ( spawnArgs.GetBool( "start_off" ) ) {
		trigger->SetContents( 0 );
		Hide();
	} else {
		trigger->SetContents( CONTENTS_TRIGGER );
	}

	giveTo = spawnArgs.GetString( "owner" );
	if ( giveTo.Length() ) {
		ent = gameLocal.FindEntity( giveTo );
		if ( !ent ) {
			gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() );
		}
		PostEventMS( &EV_Touch, 0, ent, NULL );
	}

	if ( spawnArgs.GetBool( "spin" ) || gameLocal.isMultiplayer ) {
		spin = true;
		BecomeActive( TH_THINK );
	}

	// pulse ( and therefore itemShellHandle ) was taken out and shot. do not sync
	//pulse = !spawnArgs.GetBool( "nopulse" );
	pulse = false;
	orgOrigin = GetPhysics()->GetOrigin();

	canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) );

	inViewTime = -1000;
	lastCycle = -1;
	itemShellHandle = -1;
// RAVEN BEGIN
// abahr: move texture to def file for precaching
	shellMaterial = declManager->FindMaterial( spawnArgs.GetString("mtr_highlight", "_default") );
	PostEventMS( &EV_SetGravity, 0 );
// RAVEN END
	if ( spawnArgs.GetString( "skin", NULL ) ) {
		skin = declManager->FindSkin( spawnArgs.GetString( "skin" ), false );
		if( skin ) {
			SetSkin( skin );
			srvReady = 1;
		}
	} else {
		skin = NULL;
	}

	if ( spawnArgs.GetString( "skin_pickup", NULL ) ) {
		pickupSkin = declManager->FindSkin( spawnArgs.GetString( "skin_pickup" ), false );
	} else {
		pickupSkin = NULL;
	}

	syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" );

	if ( srvReady == -1 ) {
		srvReady = IsHidden() ? 0 : 1;
	}

// RAVEN BEGIN
// mekberg: added for removing pickups in mp in pits
	if ( gameLocal.isMultiplayer ) {
		trigger->SetContents( trigger->GetContents() | CONTENTS_ITEMCLIP );
	}
// RAVEN END

	if( gameLocal.isMultiplayer ) {
		itemPVSArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() );
	} else {
		itemPVSArea = 0;
	}

	simpleItemScale = spawnArgs.GetFloat( "simple_icon_scale", "20.0" );

	simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !IsType( rvItemCTFFlag::GetClassType() );
	if( simpleItem ) {
		memset( &renderEntity, 0, sizeof( renderEntity ) );
		renderEntity.axis		= mat3_identity;
		renderEntity.shaderParms[ SHADERPARM_RED ]				= 1.0f;
		renderEntity.shaderParms[ SHADERPARM_GREEN ]			= 1.0f;
		renderEntity.shaderParms[ SHADERPARM_BLUE ]				= 1.0f;
		renderEntity.shaderParms[ SHADERPARM_ALPHA ]			= 1.0f;
		renderEntity.shaderParms[ SHADERPARM_SPRITE_WIDTH ]		= simpleItemScale;
		renderEntity.shaderParms[ SHADERPARM_SPRITE_HEIGHT ]	= simpleItemScale;
		renderEntity.hModel = renderModelManager->FindModel( "_sprite" );
		renderEntity.callback = NULL;
		renderEntity.numJoints = 0;
		renderEntity.joints = NULL;
		renderEntity.customSkin = 0;
		renderEntity.noShadow = true;
		renderEntity.noSelfShadow = true;
		renderEntity.customShader = declManager->FindMaterial( spawnArgs.GetString( "mtr_simple_icon" ) );

		renderEntity.referenceShader = 0;
		renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity );
		SetAxis( mat3_identity );
	} else {
		if ( spawnArgs.GetString( "fx_idle" ) ) {
			UpdateModelTransform();
			effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true );
		}
	}
	
	GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP );
	pickedUp = false;
}

/*
================
idItem::Event_SetGravity
================
*/
void idItem::Event_SetGravity() {
	// If the item isnt a dropped item then see if it should settle itself
	// to the floor or not
	if ( !spawnArgs.GetBool( "dropped" ) ) {
		if ( spawnArgs.GetBool( "nodrop" ) ) {
			physicsObj.PutToRest();
		} else {
	 		PostEventMS( &EV_DropToFloor, 0 );
		}
	}
}
// RAVEN END

/*
================
idItem::GetAttributes
================
*/
void idItem::GetAttributes( idDict &attributes ) {
	int					i;
	const idKeyValue	*arg;

	for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) {
		arg = spawnArgs.GetKeyVal( i );
		if ( arg->GetKey().Left( 4 ) == "inv_" ) {
			attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() );
		}
	}
}

/*
================
idItem::GiveToPlayer
================
*/
bool idItem::GiveToPlayer( idPlayer *player, bool updateHud ) {
	if ( player == NULL ) {
		return false;
	}

	if ( spawnArgs.GetBool( "inv_carry" ) ) {
		return player->GiveInventoryItem( &spawnArgs, updateHud );
	} 
	
	// Handle the special ammo pickup that gives ammo for the weapon the player currently has
	if ( spawnArgs.GetBool( "item_currentWeaponAmmo" ) ) {
		const char *ammoName = player->weapon->GetAmmoNameForIndex(player->weapon->GetAmmoType());
		if ( player->weapon->TotalAmmoCount() != player->weapon->maxAmmo && player->weapon->AmmoRequired() ) {
			player->GiveItem(ammoName);
			player->GiveInventoryItem( &spawnArgs );
			return true;
		}
		return false;
	} 

	return player->GiveItem( this, updateHud );
}

/*
===============
idItem::SendPickupMsg
===============
*/
void idItem::SendPickupMsg( int clientNum ) {
	idBitMsg	msg;
	byte		msgBuf[MAX_GAME_MESSAGE_SIZE];

	msg.Init( msgBuf, sizeof( msgBuf ) );
	msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT );
	msg.WriteBits( gameLocal.GetSpawnId( this ), 32 );
	msg.WriteByte( EVENT_PICKUP );
	msg.WriteByte( clientNum );

	// send as unreliable to client picking up the item, so it can play HUD things
	gameLocal.SendUnreliableMessagePVS( msg, this, itemPVSArea );	
}

/*
================
idItem::Pickup
================
*/
bool idItem::Pickup( idPlayer *player ) {
	//dropped weapon?
	bool dropped = spawnArgs.GetBool( "dropped" );

	if ( gameLocal.isMultiplayer && !dropped && spawnArgs.FindKey( "weaponclass" ) 
		&& gameLocal.IsWeaponsStayOn() && gameLocal.time > player->lastPickupTime + 1000 ) {
		
		idDict attr;
		GetAttributes( attr );
		const idKeyValue* arg = attr.FindKey( "weapon" );

		if ( arg ) {
			if ( !player->inventory.Give( player, player->spawnArgs, arg->GetKey(), arg->GetValue(), NULL, false, dropped, true ) ) {
				StartSound( "snd_noacquire", SND_CHANNEL_ITEM, 0, false, NULL );
			}
		}
	}

	// only predict noacquire on client
	if ( gameLocal.isClient ) {
		return false;
	}

	int givenToPlayer = spawnArgs.GetInt( "givenToPlayer", "-1" );
	if ( player == NULL || ( givenToPlayer != -1 && givenToPlayer != player->entityNumber ) ) {
		// idPlayer::GiveItem spawns an idItem for pickup, which appears at the origin before being picked
		// and could sometimes be picked by someone else, particularly in buy mode when there is a play spawn sitting on the origin ;-)
		return false;
	}

	if ( !GiveToPlayer( player ) ) {
		return false;
	}

	if ( gameLocal.isServer ) {
		SendPickupMsg( player->entityNumber );
	}

	// Check for global acquire sounds in multiplayer
 	if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) {
		gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber );
	} else {
		StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL );
	}
		
	// trigger our targets
	ActivateTargets( player );

	player->lastPickupTime = gameLocal.time;

	//if a placed item and si_weaponStay is on and we're a weapon, don't remove and respawn
	if ( gameLocal.IsMultiplayer() ) {
		if ( !dropped )	{
			if ( spawnArgs.FindKey( "weaponclass" ) ) { 
				if ( gameLocal.IsWeaponsStayOn() ) {
					return true;
				}
			}
		}
	}

	// clear our contents so the object isn't picked up twice
	GetPhysics()->SetContents( 0 );

	// hide the model, or switch to the pickup skin
	if ( pickupSkin ) {
		SetSkin( pickupSkin );
		srvReady = 0;
	} else {
		Hide();
		BecomeInactive( TH_THINK );
	}

	pickedUp = true;

	// allow SetSkin or Hide() to get called regardless of simpleitem mode
	if( simpleItem ) {
		FreeModelDef();
		UpdateVisuals();
	}

	// remove the highlight shell
	if ( itemShellHandle != -1 ) {
		gameRenderWorld->FreeEntityDef( itemShellHandle );
		itemShellHandle = -1;
	}

	// asalmon: Added option for a differnt respawn rate based on gametype.
	float respawn = spawnArgs.GetFloat(va("respawn_%s",gameLocal.serverInfo.GetString( "si_gameType" )), "-1.0");
	if( respawn == -1.0f ) {
		respawn = spawnArgs.GetFloat( "respawn", "5.0" );
	}

	bool no_respawn = spawnArgs.GetBool( "no_respawn" );

	if ( !gameLocal.isMultiplayer ) {
		respawn = 0.0f;
	} else if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) {
		if ( givenToPlayer != -1 ) {
			respawn = 0.0f;
		}
	}

	if ( respawn && !dropped && !no_respawn ) {
		const char *sfx = spawnArgs.GetString( "fx_Respawn" );
		if ( sfx && *sfx ) {
			PostEventSec( &EV_RespawnFx, respawn - 0.5f );
		}
		PostEventSec( &EV_RespawnItem, respawn );
	} else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) {
		// give some time for the pickup sound to play
		// FIXME: Play on the owner
		if ( !spawnArgs.GetBool( "inv_carry" ) ) {
			PostEventMS( &EV_Remove, 5000 );
		}
	}
	
	trigger->SetContents( 0 );	
	
	StopEffect( "fx_idle" );

	return true;
}

/*
================
idItem::Hide
================
*/
void idItem::Hide( void ) {
	srvReady = 0;
	idEntity::Hide( );	
	trigger->SetContents( 0 );
}

/*
================
idItem::Show
================
*/
void idItem::Show( void ) {
	srvReady = 1;
	idEntity::Show( );
	trigger->SetContents( CONTENTS_TRIGGER );
}

/*
================
idItem::ClientStale
================
*/
bool idItem::ClientStale( void ) {
	idEntity::ClientStale();

	StopEffect( "fx_idle" );
	return false;
}

/*
================
idItem::ClientUnstale
================
*/
void idItem::ClientUnstale( void ) {
	idEntity::ClientUnstale();

	UpdateModelTransform();
	if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) {
		PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true );
	}
}

/*
================
idItem::ClientPredictionThink
================
*/
void idItem::ClientPredictionThink( void ) {
	// only think forward because the state is not synced through snapshots
	if ( !gameLocal.isNewFrame ) {
		return;
	}
	Think();
}

/*
================
idItem::WriteFromSnapshot
================
*/
void idItem::WriteToSnapshot( idBitMsgDelta &msg ) const {
	if ( syncPhysics ) {
		physicsObj.WriteToSnapshot( msg );
	}
	assert( srvReady != -1 );
	msg.WriteBits( ( srvReady == 1 ), 1 );
}

/*
================
idItem::ReadFromSnapshot
================
*/
void idItem::ReadFromSnapshot( const idBitMsgDelta &msg ) {
	if ( syncPhysics ) {
		physicsObj.ReadFromSnapshot( msg );
	}
	int newReady = ( msg.ReadBits( 1 ) != 0 );
	idVec3 resetOrigin( 0, 0, 0 );
	// client spawns the ent with ready == -1 so the state set happens at least once
	if ( newReady != clReady ) {
		if ( newReady ) {
			// g_simpleItems might force a hide even with a pickup skin
			if ( pickupSkin ) {
				SetSkin( skin );
				srvReady = 1;
			} else {
				SetSkin( skin );
				Show();
			}

			if( simpleItem ) {
				UpdateVisuals();			
			}
			pickedUp = false;

			if ( effectIdle.GetEntity( ) ) {
				UpdateModelTransform();
				effectIdle->SetOrigin( resetOrigin );
			} else if ( spawnArgs.GetString( "fx_idle" ) && !simpleItem ) {
				UpdateModelTransform();
				effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true );
			}
		} else {
			if ( pickupSkin ) {
				SetSkin( pickupSkin );
				srvReady = 0;
			} else {
				Hide();
			}

			if( simpleItem ) {
				FreeModelDef();
				UpdateVisuals();
			}
			
			pickedUp = true;

			StopEffect( "fx_idle" );
			effectIdle = NULL;
		}
	}
	clReady = newReady;
}

/*
================
idItem::Event_Pickup
================
*/
void idItem::Event_Pickup( int clientNum ) {
	idPlayer *player;

	assert( gameLocal.isClient );
	
	// play pickup sound
	if ( !spawnArgs.GetBool( "globalAcquireSound" ) ) {
		StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL );
	}
	
	if( clientNum == gameLocal.localClientNum ) {
		player = (idPlayer*)gameLocal.entities[ clientNum ];
		player->lastPickupTime = gameLocal.time;
		if ( player ) {
			player->GiveItem( this );
		}
	}
}

/*
================
idItem::ClientReceiveEvent
================
*/
bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
	switch( event ) {
		case EVENT_PICKUP: {
			int clientNum = msg.ReadByte();
			if( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
				Event_Pickup( clientNum );
			}
			return true;
		}
		case EVENT_RESPAWNFX: {
			Event_RespawnFx();
			return true;
		}
		default: {
			return idEntity::ClientReceiveEvent( event, time, msg );
		}
	}
//unreachable
//	return false;
}

/*
================
idItem::Event_DropToFloor
================
*/
void idItem::Event_DropToFloor( void ) {
 	// don't drop the floor if bound to another entity
 	if ( GetBindMaster() != NULL && GetBindMaster() != this ) {
 		return;
 	}
 	
 	physicsObj.DropToFloor( );
}

/*
================
idItem::Event_Touch
================
*/
void idItem::Event_Touch( idEntity *other, trace_t *trace ) {
// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
	if ( !other->IsType( idPlayer::GetClassType() ) ) {
// RAVEN END
		return;
	}

	if ( !canPickUp ) {
		return;
	}

	Pickup( static_cast<idPlayer *>(other) );
}

/*
================
idItem::Event_Trigger
================
*/
void idItem::Event_Trigger( idEntity *activator ) {
	if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) {
		canPickUp = true;
		return;
	}

// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
	if ( activator && activator->IsType( idPlayer::GetClassType() ) ) {
// RAVEN END
		Pickup( static_cast<idPlayer *>( activator ) );
	}
}

/*
================
idItem::Event_Respawn
================
*/
void idItem::Event_Respawn( void ) {

	// with simple items, re-show the item, but still set srvReady to true to let clients
	// know how to deal
	if ( pickupSkin ) {
		srvReady = true;
		if( !simpleItem ) {
			SetSkin( skin );
		}
	} 
	
	if( !pickupSkin ) {
		BecomeActive( TH_THINK );	
		Show();
	}

	if( simpleItem ) {
		Show();
	}

	pickedUp = false;

	inViewTime = -1000;
	lastCycle = -1;
	trigger->SetContents ( CONTENTS_TRIGGER );
	SetOrigin( orgOrigin );
	StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL );
	PostEventMS( &EV_SetGravity, 0 );
	CancelEvents( &EV_RespawnItem ); // don't double respawn

	if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) {
		UpdateModelTransform();
		PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true );
	}
}

/*
================
idItem::Event_RespawnFx
================
*/
void idItem::Event_RespawnFx( void ) {
	idBitMsg	msg;
	byte		msgBuf[MAX_GAME_MESSAGE_SIZE];

	if ( gameLocal.isServer ) {
		msg.Init( msgBuf, sizeof( msgBuf ) );
		msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT );
		msg.WriteBits( gameLocal.GetSpawnId( this ), 32 );
		msg.WriteByte( EVENT_RESPAWNFX );
		// send unreliable PVS-sensitive
		gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ) );
	}

	if( !simpleItem ) {
		gameLocal.PlayEffect( spawnArgs, "fx_respawn", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin );
	} else {
		// zinx - HACK - hardcoded sound to avoid new defs for 1.4.  Please ensure it remains precached.
		// FIXME - put the sound in the defs
		if ( !idStr::Icmp( spawnArgs.GetString( "fx_respawn" ), "effects/mp/itemrespawn.fx" ) ) {
			const idSoundShader *shader = declManager->FindSound( "mp_item_respawn" );
			StartSoundShader( shader, SND_CHANNEL_ITEM, 0, false, NULL );
		}
	}
}

/*
===============================================================================

  idItemPowerup

===============================================================================
*/

/*
===============
idItemPowerup
===============
*/

CLASS_DECLARATION( idItem, idItemPowerup )
END_CLASS

/*
================
idItemPowerup::idItemPowerup
================
*/
idItemPowerup::idItemPowerup() {
	time = 0;
	type = 0;
	droppedTime = 0;
	unique = false;
}

/*
================
idItemPowerup::Save
================
*/
void idItemPowerup::Save( idSaveGame *savefile ) const {
	savefile->WriteInt( time );
	savefile->WriteInt( type );
	savefile->WriteInt( droppedTime );	// cnicholson: Added unsaved var
}

/*
================
idItemPowerup::Restore
================
*/
void idItemPowerup::Restore( idRestoreGame *savefile ) {
	savefile->ReadInt( time );
	savefile->ReadInt( type );
	savefile->ReadInt( droppedTime );	// cnicholson: Added unrestored var
}

/*
================
idItemPowerup::Spawn
================
*/
void idItemPowerup::Spawn( void ) {
	time = SEC2MS( spawnArgs.GetInt( "time", "30" ) );
	// SEC2MS screws up when we want -1 time (no expiration)
	if( spawnArgs.GetInt( "time" ) == -1 ) {
		time = -1;
	}

	type = spawnArgs.GetInt( "type", "0" );
	
	// If the powerup was dropped then make it dissapear using its remaining time.
	if ( spawnArgs.GetBool( "dropped" ) && time != -1 ) {
		droppedTime = gameLocal.time + time;
		PostEventMS( &EV_Remove, time );
	}

	// unique powerpus won't respawn while a player has them
	unique = spawnArgs.GetBool( "unique", "0" );
	if( unique ) {
		spawnArgs.SetBool( "no_respawn", true );
	}

	if ( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) {
		team = TEAM_STROGG;
	} else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) {
		team = TEAM_MARINE;
	} else {
		team = -1;
	}
}

/*
================
idItemPowerup::GiveToPlayer
================
*/
bool idItemPowerup::GiveToPlayer( idPlayer *player, bool updateHud ) {
	if ( player == NULL || player->spectating ) {
		return false;
	}

	// only one arena CTF powerup at a time
	if ( type >= POWERUP_AMMOREGEN && type <= POWERUP_SCOUT ) {
		if ( ( player->inventory.powerups & ARENA_POWERUP_MASK ) != 0 ) {
			return false;
		} 
	}

	// in flavours of arena CTF (or are idItemPowerups only used in Arena? or even, are idItemPowerups MP only?), 
	//	ensure that items with a team can only be picked up by members of that team
	if ( gameLocal.IsMultiplayer() && gameLocal.IsTeamGame() && team >= 0 ) {
		if( team != player->team ) {
			return false;
		}
	}

	if ( droppedTime > 0 ) {
		player->GivePowerUp( type, droppedTime - gameLocal.time );
	} else {
		player->GivePowerUp( type, time );
	}

	// also call idItem::GiveToPlayer so any inv_* keywords get applied
	idItem::GiveToPlayer( player, updateHud );

	return true;
}

/*
================
idItemPowerup::Think
================
*/
void idItemPowerup::Think( void ) {
	int i;

	// idItem::Think() only needs to be called if we're spawned in
	if( !IsHidden() || (pickupSkin && GetSkin() != pickupSkin ) ) {
		// only get here if spawned in
		idItem::Think();
		return;
	}

	if( !unique ) {
		// non-unique despawned powerups don't need to think
		return;
	}

	for( i = 0; i < gameLocal.numClients; i++ ) {
		idPlayer* p = (idPlayer*)gameLocal.entities[ i ];
		if( p == NULL ) {
			continue;
		}

		// only spawn back in if noone on your team has the powerup
		if( p->PowerUpActive( type ) && p->team == team ) {
			break;
		}
	}

	if( i >= gameLocal.numClients ) {
		PostEventMS( &EV_RespawnItem, 0 );
	}
}

/*
================
idItemPowerup::Pickup
================
*/
bool idItemPowerup::Pickup( idPlayer* player ) {
	// regular pickup routine, but unique items need to think to know when to respawn
	bool pickup;
	
	if( gameLocal.isClient ) {
		// no client-side powerup prediction
		return false;
	}

	pickup = idItem::Pickup( player );
	if( unique ) {
		BecomeActive( TH_THINK );
	}

	return pickup;
}

/*
===============================================================================

  idObjective

===============================================================================
*/

CLASS_DECLARATION( idItem, idObjective )
	EVENT( EV_Activate,			idObjective::Event_Trigger )
	EVENT( EV_HideObjective,	idObjective::Event_HideObjective )
	EVENT( EV_GetPlayerPos,		idObjective::Event_GetPlayerPos )
 	EVENT( EV_CamShot,			idObjective::Event_CamShot )
END_CLASS

/*
================
idObjective::idObjective
================
*/
idObjective::idObjective() {
	playerPos.Zero();

// RAVEN BEGIN
// mekberg: store triggered time for timed removal.
	triggerTime = 0;
// RAVEN END
}

/*
================
idObjective::Save
================
*/
void idObjective::Save( idSaveGame *savefile ) const {
	savefile->WriteVec3( playerPos );
}

/*
================
idObjective::Restore
================
*/
void idObjective::Restore( idRestoreGame *savefile ) {
	savefile->ReadVec3( playerPos );
// RAVEN BEGIN
// jnewquist: Don't do this on Xenon, we want prebuilt textures
#ifndef _XENON
 	PostEventMS( &EV_CamShot, 250 );
#endif
// RAVEN END
}

/*
================
idObjective::Spawn
================
*/
void idObjective::Spawn( void ) {
	Hide();
// RAVEN BEGIN
// jnewquist: Only post a camshot event if the spawn args request it
#ifndef _XENON
	const char *camName;
	if ( spawnArgs.GetString( "camShot", "", &camName ) ) {
		common->Warning( "SpawnArg camShot on %s is not recommended.", spawnArgs.GetString( "name" ) );
 		PostEventMS( &EV_CamShot, 250 );
	}
#endif
// RAVEN END
}

/*
================
idObjective::Event_Screenshot
================
*/
void idObjective::Event_CamShot( ) {
	const char *camName;
// RAVEN BEGIN
// bdube: changed screenshot location
 	idStr shotName = "gfx/objectives/";
 	shotName += spawnArgs.GetString( "screenshot" );
 	shotName.SetFileExtension( ".tga" );
 // RAVEN END
	if ( spawnArgs.GetString( "camShot", "", &camName ) ) {
		idEntity *ent = gameLocal.FindEntity( camName );
		if ( ent && ent->cameraTarget ) {
			const renderView_t *view = ent->cameraTarget->GetRenderView();
			renderView_t fullView = *view;
			fullView.width = SCREEN_WIDTH;
			fullView.height = SCREEN_HEIGHT;

			// draw a view to a texture
			renderSystem->CropRenderSize( 256, 256, true );
			gameRenderWorld->RenderScene( &fullView );
			// TTimo: I don't think this jives with SMP code, but I grepped through the final maps and didn't see any using it
 			renderSystem->CaptureRenderToFile( shotName );
			renderSystem->UnCrop();
		}
	}
}  

/*
================
idObjective::Event_Trigger
================
*/
void idObjective::Event_Trigger( idEntity *activator ) {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player ) {
		if ( spawnArgs.GetString( "inv_objective", NULL ) ) {
// RAVEN BEGIN
// abahr: changed player->hud to player->GetHud so when in a vehicle we update the vehicle hud
// twhitaker: changed player->hud to player->GetObjectiveHud(), to resolve all issues
	 		if ( player && player->GetObjectiveHud() ) {
// bdube: changed screenshot location
				idStr shotName = spawnArgs.GetString( "screenshot" );

				player->GetObjectiveHud()->SetStateString( "screenshot", shotName );
				player->GetObjectiveHud()->SetStateString( "objective", "1" );
				player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) );
				player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) );
// RAVEN END
 				player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), shotName );

				// a tad slow but keeps from having to update all objectives in all maps with a name ptr
				for( int i = 0; i < gameLocal.num_entities; i++ ) {
// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
					if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::GetClassType() ) ) {
// RAVEN END
						if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){
							gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true );
							break;
						}
					}
				}

				PostEventMS( &EV_GetPlayerPos, 2000 );
// RAVEN BEGIN
// mekberg: store triggered time for timed removal.
				triggerTime = gameLocal.time;
// RAVEN END
			}
		}
	}
}

/*
================
idObjective::Event_GetPlayerPos
================
*/
void idObjective::Event_GetPlayerPos() {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player ) {
		playerPos = player->GetPhysics()->GetOrigin();
		PostEventMS( &EV_HideObjective, 100, player );
	}
}

/*
================
idObjective::Event_HideObjective
================
*/
void idObjective::Event_HideObjective(idEntity *e) {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player ) {
		idVec3 v = player->GetPhysics()->GetOrigin() - playerPos;
// RAVEN BEGIN
// mekberg: hide time done internally now
		if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) {
// RAVEN END
			player->HideObjective ( );
			PostEventMS( &EV_Remove, 0 );
		} else {
			PostEventMS( &EV_HideObjective, 100, player );
		}
	}
}

/*
===============================================================================

  idMoveableItem
	
===============================================================================
*/

CLASS_DECLARATION( idItem, idMoveableItem )
 	EVENT( EV_Gib,			idMoveableItem::Event_Gib )
END_CLASS

/*
================
idMoveableItem::idMoveableItem
================
*/
idMoveableItem::idMoveableItem() {
}

/*
================
idMoveableItem::~idMoveableItem
================
*/
idMoveableItem::~idMoveableItem() {
	// If this entity has been allocated, but not spawned, the physics object will
	// not have a self pointer, and will not unregister itself from the entity.
	SetPhysics( NULL );
}

/*
================
idMoveableItem::Save
================
*/
void idMoveableItem::Save( idSaveGame *savefile ) const {
   	savefile->WriteStaticObject( physicsObj );
}

/*
================
idMoveableItem::Restore
================
*/
void idMoveableItem::Restore( idRestoreGame *savefile ) {
	savefile->ReadStaticObject ( physicsObj );
	RestorePhysics ( &physicsObj );
}

/*
================
idMoveableItem::Spawn
================
*/
void idMoveableItem::Spawn( void ) {
	idTraceModel trm;
	float density, friction, bouncyness;
	idStr clipModelName;
	idBounds bounds;

	// if the model should be shrinked
	if ( spawnArgs.GetBool( "clipshrink" ) ) {
		trm.Shrink( CM_CLIP_EPSILON );
	}

	// get rigid body properties
	spawnArgs.GetFloat( "density", "0.5", density );
	density = idMath::ClampFloat( 0.001f, 1000.0f, density );
	spawnArgs.GetFloat( "friction", "0.05", friction );
	friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
	spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness );
	bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );

	// setup the physics
	physicsObj.SetSelf( this );

// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_PUSH_HEAP_MEM(this);
	// check if a clip model is set
	spawnArgs.GetString( "itemclipmodel", "", clipModelName );
	if ( clipModelName[0] ) {
		if ( collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) {
			physicsObj.SetClipModel( new idClipModel( trm ), density );
		} else {
			// fallback
			physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density );
		}
	} else {
		// fallback
		physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density );
	}

// mwhitlock: Dynamic memory consolidation
	RV_POP_HEAP();
// RAVEN END
	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
	physicsObj.SetAxis( GetPhysics()->GetAxis() );
	physicsObj.SetBouncyness( bouncyness );
	physicsObj.SetFriction( 0.6f, 0.6f, friction );
	physicsObj.SetGravity( gameLocal.GetGravity() );
	physicsObj.SetContents( CONTENTS_RENDERMODEL );
	physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
	SetPhysics( &physicsObj );

// RAVEN BEGIN
// mekberg: added
	if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) {
		physicsObj.DisableImpact();
	}
// RAVEN END
}

/*
================
idMoveableItem::Think
================
*/
void idMoveableItem::Think( void ) {
	RunPhysics();
	UpdateTrigger( );
	Present();
}

/*
================
idMoveableItem::DropItem
================
*/
idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) {
	idDict args;
	idEntity *item;

	args.Set( "classname", classname );
	args.Set( "dropped", "1" );

 	// we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor
 	args.Set( "nodrop", "1" ); 

	if ( activateDelay ) {
		args.SetBool( "triggerFirst", true );
	}

	gameLocal.SpawnEntityDef( args, &item );
	if ( item ) {
		// set item position
		item->GetPhysics()->SetOrigin( origin );
		item->GetPhysics()->SetAxis( axis );
		item->GetPhysics()->SetLinearVelocity( velocity );
 		item->UpdateVisuals();
		if ( activateDelay ) {
			item->PostEventMS( &EV_Activate, activateDelay, item );
		}
		if ( !removeDelay ) {
			removeDelay = 5 * 60 * 1000;
		}
		// always remove a dropped item after 5 minutes in case it dropped to an unreachable location
		item->PostEventMS( &EV_Remove, removeDelay );
	}

 	return item;
}

/*
================
idMoveableItem::DropItems

  The entity should have the following key/value pairs set:
 	"def_drop<type>Item"			"item def"
 	"drop<type>ItemJoint"			"joint name"
 	"drop<type>ItemRotation"		"pitch yaw roll"
 	"drop<type>ItemOffset"			"x y z"
 	"skin_drop<type>"				"skin name"
  To drop multiple items the following key/value pairs can be used:
 	"def_drop<type>Item<X>"			"item def"
 	"drop<type>Item<X>Joint"		"joint name"
 	"drop<type>Item<X>Rotation"		"pitch yaw roll"
 	"drop<type>Item<X>Offset"		"x y z"
  where <X> is an aribtrary string.
================
*/
void idMoveableItem::DropItems( idAnimatedEntity  *ent, const char *type, idList<idEntity *> *list ) {
	const idKeyValue *kv;
	const char *skinName, *c, *jointName;
	idStr key, key2;
	idVec3 origin;
	idMat3 axis;
	idAngles angles;
	const idDeclSkin *skin;
	jointHandle_t joint;
	idEntity *item;

	// drop all items
	kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL );
	while ( kv ) {

		c = kv->GetKey().c_str() + kv->GetKey().Length();
		if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) {

			key = kv->GetKey().c_str() + 4;
			key2 = key;
			key += "Joint";
			key2 += "Offset";
			jointName = ent->spawnArgs.GetString( key );
			joint = ent->GetAnimator()->GetJointHandle( jointName );
			if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) {
				gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() );
				origin = ent->GetPhysics()->GetOrigin();
				axis = ent->GetPhysics()->GetAxis();
			}
			if ( g_dropItemRotation.GetString()[0] ) {
				angles.Zero();
				sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll );
			} else {
				key = kv->GetKey().c_str() + 4;
				key += "Rotation";
				ent->spawnArgs.GetAngles( key, "0 0 0", angles );
			}
			axis *= angles.ToMat3() * axis;

			origin += ent->spawnArgs.GetVector( key2, "0 0 0" );

			item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 );
			if ( list && item ) {
				list->Append( item );
			}
		}

		kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv );
	}

	// change the skin to hide all items
 	skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) );
	if ( skinName[0] ) {
		skin = declManager->FindSkin( skinName );
		ent->SetSkin( skin );
	}
}

/*
======================
idMoveableItem::WriteToSnapshot
======================
*/
void idMoveableItem::WriteToSnapshot( idBitMsgDelta &msg ) const {
	physicsObj.WriteToSnapshot( msg );
}

/*
======================
idMoveableItem::ReadFromSnapshot
======================
*/
void idMoveableItem::ReadFromSnapshot( const idBitMsgDelta &msg ) {
	physicsObj.ReadFromSnapshot( msg );
 	if ( msg.HasChanged() ) {
		UpdateVisuals();
 	}
}

/*
============
idMoveableItem::Gib
============
*/
void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) {
	// remove the entity
	PostEventMS( &EV_Remove, 0 );
}

/*
============
idMoveableItem::Event_Gib
============
*/
void idMoveableItem::Event_Gib( const char *damageDefName ) {
	Gib( idVec3( 0, 0, 1 ), damageDefName );
}

/*
===============================================================================

  idItemRemover

===============================================================================
*/

CLASS_DECLARATION( idEntity, idItemRemover )
	EVENT( EV_Activate,		idItemRemover::Event_Trigger )
END_CLASS

/*
================
idItemRemover::Spawn
================
*/
void idItemRemover::Spawn( void ) {
}

/*
================
idItemRemover::RemoveItem
================
*/
void idItemRemover::RemoveItem( idPlayer *player ) {
	const char *remove;
	
	remove = spawnArgs.GetString( "remove" );
	player->RemoveInventoryItem( remove );
}

/*
================
idItemRemover::Event_Trigger
================
*/
void idItemRemover::Event_Trigger( idEntity *activator ) {
// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
	if ( activator->IsType( idPlayer::GetClassType() ) ) {
// RAVEN END
		RemoveItem( static_cast<idPlayer *>(activator) );
	}
}

/*
===============================================================================

  idObjectiveComplete

===============================================================================
*/

CLASS_DECLARATION( idItemRemover, idObjectiveComplete )
	EVENT( EV_Activate,			idObjectiveComplete::Event_Trigger )
	EVENT( EV_HideObjective,	idObjectiveComplete::Event_HideObjective )
	EVENT( EV_GetPlayerPos,		idObjectiveComplete::Event_GetPlayerPos )
END_CLASS

/*
================
idObjectiveComplete::idObjectiveComplete
================
*/
idObjectiveComplete::idObjectiveComplete() {
	playerPos.Zero();

// RAVEN BEGIN
// mekberg: store triggered time for timed removal.
	triggerTime = 0;
// RAVEN END
}

/*
================
idObjectiveComplete::Save
================
*/
void idObjectiveComplete::Save( idSaveGame *savefile ) const {
	savefile->WriteVec3( playerPos );
}

/*
================
idObjectiveComplete::Restore
================
*/
void idObjectiveComplete::Restore( idRestoreGame *savefile ) {
	savefile->ReadVec3( playerPos );
}

/*
================
idObjectiveComplete::Spawn
================
*/
void idObjectiveComplete::Spawn( void ) {
 	spawnArgs.SetBool( "objEnabled", false );
	Hide();
}

/*
================
idObjectiveComplete::Event_Trigger
================
*/
void idObjectiveComplete::Event_Trigger( idEntity *activator ) {
 	if ( !spawnArgs.GetBool( "objEnabled" ) ) {
 		return;
 	}
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player ) {
		RemoveItem( player );

		if ( spawnArgs.GetString( "inv_objective", NULL ) ) {
// RAVEN BEGIN
// abahr: changed player->hud to player->GetHud so if in a vehicle we update that hud
// twhitaker: moved objective system to it's own hud, to solve vehicle hud issues.
	 		if ( player->GetObjectiveHud() ) {
				player->GetObjectiveHud()->SetStateString( "objective", "2" );
				player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) );
				player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) );
// RAVEN END
				player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) );
				PostEventMS( &EV_GetPlayerPos, 2000 );
// RAVEN BEGIN
// mekberg: store triggered time for timed removal.
				triggerTime = gameLocal.time;
// RAVEN END
			}
		}
	}
}

/*
================
idObjectiveComplete::Event_GetPlayerPos
================
*/
void idObjectiveComplete::Event_GetPlayerPos() {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player ) {
		playerPos = player->GetPhysics()->GetOrigin();
		PostEventMS( &EV_HideObjective, 100, player );
	}
}

/*
================
idObjectiveComplete::Event_HideObjective
================
*/
void idObjectiveComplete::Event_HideObjective( idEntity *e ) {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( player && player->GetObjectiveHud() ) {
		idVec3 v = player->GetPhysics()->GetOrigin();
		v -= playerPos;
// RAVEN BEGIN
// mekberg: hide time done internally now
		if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) {
// RAVEN END
			player->HideObjective ( );
			PostEventMS( &EV_Remove, 0 );
		} else {
			PostEventMS( &EV_HideObjective, 100, player );
		}
	}
}

/*
===============================================================================

  rvObjectiveFailed

===============================================================================
*/
CLASS_DECLARATION( idItemRemover, rvObjectiveFailed )
	EVENT( EV_Activate,			rvObjectiveFailed::Event_Trigger )
END_CLASS

rvObjectiveFailed::rvObjectiveFailed ( void ) {
}

/*
================
rvObjectiveFailed::Event_Trigger
================
*/
void rvObjectiveFailed::Event_Trigger( idEntity *activator ) {
	idPlayer *player = gameLocal.GetLocalPlayer();
	if ( !player || !player->GetObjectiveHud() ) {
		return;
	}
	if ( !spawnArgs.GetString( "inv_objective", NULL ) ) {
		return;
	}	

	player->GetObjectiveHud()->SetStateString( "objective", "2" );
	player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) );
	player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) );
	player->FailObjective( spawnArgs.GetString( "objectivetitle" ) );
}

/*
===============================================================================

  rvItemCTFFlag

===============================================================================
*/

const idEventDef EV_ResetFlag ( "<resetflag>" );
const idEventDef EV_LinkTrigger( "<linktrigger>" );
CLASS_DECLARATION( idItem, rvItemCTFFlag )
	EVENT( EV_ResetFlag,		rvItemCTFFlag::Event_ResetFlag )
	EVENT( EV_LinkTrigger,		rvItemCTFFlag::Event_LinkTrigger )
END_CLASS

/*
================
rvItemCTFFlag::rvItemCTFFlag
================
*/
rvItemCTFFlag::rvItemCTFFlag() {
}


/*
================
rvItemCTFFlag::Spawn
================
*/
void rvItemCTFFlag::Spawn () {
/* rjohnson: I fixed the crash so we don't need to remove them...
	//don't spawn outside of CTF games
	if( (gameLocal.gameType != GAME_CTF) && (gameLocal.gameType != GAME_1F_CTF) &&
		(gameLocal.gameType != GAME_ARENA_CTF) && (gameLocal.gameType != GAME_ARENA_1F_CTF) ){
		//don't spawn!
		gameLocal.Error("Flag spawn on in non-CTF gametype.");
		PostEventMS ( &EV_Remove, 0 );
		return;
	}
*/
	spawnArgs.GetBool ( "dropped", "0", dropped );
	spawnArgs.GetInt ( "team", "0", team );

	bool reset = false;
	spawnArgs.GetBool ( "reset", "0", reset );

	switch ( team ) {
		case TEAM_MARINE: {
			powerup = POWERUP_CTF_MARINEFLAG;
			gameLocal.mpGame.SetFlagEntity( this, TEAM_MARINE );
			break;
		}
		case TEAM_STROGG: {
			powerup = POWERUP_CTF_STROGGFLAG;	
			gameLocal.mpGame.SetFlagEntity( this, TEAM_STROGG );
			break;
		}
		case TEAM_MAX: {
			powerup = POWERUP_CTF_ONEFLAG;
			break;
		}
		default:
			gameLocal.Warning ( "Unknown ctf flag team '%d' on entity '%s'", team, name.c_str() );
			PostEventMS ( &EV_Remove, 0 );
			return;
	}
	
	if ( dropped ) {
		if ( !gameLocal.isClient ) {
			((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_DROPPED );
		}	
		if ( reset ) {
			//PostEventSec( &EV_ResetFlag, 1 );
		} else {
			PostEventSec( &EV_ResetFlag, 30 );
		}

		// Let powerups settle for a frame before allowing them to be picked up - we need to
		// make sure we hit the dropped state for VO, etc to work properly
		trigger->SetContents( 0 );
		PostEventSec( &EV_LinkTrigger, 0 );
	} else {
		if ( !gameLocal.isClient ) {
			((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE );
		}	
	}

	GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP );

	spin = false;
}

/*
================
rvItemCTFFlag::Event_LinkTrigger
================
*/
void rvItemCTFFlag::Event_LinkTrigger( void ) {
	trigger->SetContents( CONTENTS_TRIGGER | CONTENTS_ITEMCLIP );
}

/*
================
rvItemCTFFlag::GiveToPlayer
================
*/
bool rvItemCTFFlag::GiveToPlayer( idPlayer* player, bool updateHud ) {
	(void)updateHud;
	if ( !gameLocal.IsMultiplayer() ) {
		return false;
	}

	if ( player->spectating || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && (gameLocal.mpGame.GetGameState())->GetMPGameState() != SUDDENDEATH && !cvarSystem->GetCVarBool( "g_testCTF" )) ) {
		return false;
	}

	int teamPowerup;
	int enemyPowerup;

	bool canPickup = ( team == player->team );
	
	switch ( player->team ) {
		default:
		case TEAM_MARINE:
			teamPowerup  = POWERUP_CTF_MARINEFLAG;
			enemyPowerup = POWERUP_CTF_STROGGFLAG;
			break;
			
		case TEAM_STROGG:
			teamPowerup  = POWERUP_CTF_STROGGFLAG;
			enemyPowerup = POWERUP_CTF_MARINEFLAG;
			break;
	}

	if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) {
		// in one flag CTF, we score touching the enemy's flag
		canPickup = ( team == gameLocal.mpGame.OpposingTeam( player->team ) );
		enemyPowerup = POWERUP_CTF_ONEFLAG;
	}
	
	// If the player runs over their own flag the only thing they
	// can do is score or return it.

	// when the player touches the one flag, he always gets it
	if ( canPickup ) {
		// If the flag was dropped, return it		
		if ( dropped ) {
			gameLocal.mpGame.AddPlayerTeamScore( player, 2 );
			statManager->FlagReturned( player );
			ResetFlag ( teamPowerup );
// RITUAL BEGIN
// squirrel: Mode-agnostic buymenus
			player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagReturned", 0 ) );
// RITUAL END
		} else if ( player->PowerUpActive ( enemyPowerup ) ) {
			// If they have the enemy flag then they score
			if ( !gameLocal.mpGame.CanCapture ( player->team ) ) {
				return false;
			}
			
			ResetFlag( enemyPowerup );
			
			gameLocal.mpGame.FlagCaptured( player );
		}
		return false;
	} 

	// only pickup one flag in arena CTF
	if( ( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) && team != TEAM_MAX ) {
		return false;
	}

	player->GivePowerUp( enemyPowerup, -1 );
// RITUAL BEGIN
// squirrel: Mode-agnostic buymenus
	player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagStolen", 0 ) );
// RITUAL END
	return true;
}

/*
================
rvItemCTFFlag::Pickup
================
*/
bool rvItemCTFFlag::Pickup( idPlayer *player ) {
	if( gameLocal.isClient ) {
		// no client-side CTF flag prediction
		return false;
	}

	if ( !GiveToPlayer( player ) ) {
		return false;
	}

	//	ServerSendEvent( EVENT_PICKUP, NULL, false, -1 );
	if ( gameLocal.isServer ) {
		SendPickupMsg( player->entityNumber );
	}

	// Check for global acquire sounds in multiplayer
	if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) {
		gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber );
	} else {
		StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL );
	}

	// trigger our targets
	ActivateTargets( player );

	// clear our contents so the object isn't picked up twice
	GetPhysics()->SetContents( 0 );

	// hide the model
	Hide();

	gameLocal.mpGame.SetFlagEntity( NULL, team );

	gameLocal.mpGame.AddPlayerTeamScore( player, 1 );

	if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_ARENA_CTF ) { 
		((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_TAKEN );
	} else if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) {
		((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, (player->team == TEAM_MARINE ? FS_TAKEN_MARINE : FS_TAKEN_STROGG) );
	}
	
	((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagCarrier( team, player->entityNumber );

	if ( spawnArgs.GetBool( "dropped" ) ) {
		PostEventMS( &EV_Remove, 0 );
	}

	BecomeInactive( TH_THINK );
	
	return true;
}

/*
================
rvItemCTFFlag::ResetFlag
================
*/
void rvItemCTFFlag::ResetFlag( int powerup ) {
	idEntity* ent;
	for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
		// Make sure no players have the flag anymore
// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
		if ( ent->IsType ( idPlayer::GetClassType() ) ) {
// RAVEN END
			static_cast<idPlayer*>(ent)->ClearPowerup ( powerup );
			continue;
		}
	
		// If its not a CTF flag item then skip it
		if ( !ent->IsType( rvItemCTFFlag::Type ) ) {
			continue;			
		}
		
		// Make sure its the right type first
		rvItemCTFFlag* flag;
		flag = static_cast<rvItemCTFFlag*>(ent);
		if ( flag->powerup != powerup ) {
			continue;
		}
		
		if ( flag->dropped ) {			
			flag->PostEventMS( &EV_Remove, 0 );
		} else {
			flag->PostEventMS( &EV_RespawnItem, 0 );
			gameLocal.mpGame.SetFlagEntity( flag, flag->team );
		}
	}

	if ( !gameLocal.isClient ) {
		int team = -1;
		if ( powerup == POWERUP_CTF_MARINEFLAG ) {
			team = TEAM_MARINE;
		} else if ( powerup == POWERUP_CTF_STROGGFLAG ) {
			team = TEAM_STROGG;
		} else if ( powerup == POWERUP_CTF_ONEFLAG ) {
			team = TEAM_MAX;
		}
		
		((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE );
	}	

}
  
/*
================
rvItemCTFFlag::Event_ResetFlag
================
*/
void rvItemCTFFlag::Event_ResetFlag( void ) {
	ResetFlag( powerup );
}

void rvItemCTFFlag::Think( void ) {
	idItem::Think();
}

/*
================
rvItemCTFFlag::Collide
================
*/
bool rvItemCTFFlag::Collide( const trace_t &collision, const idVec3 &velocity ) {
	idEntity* lol = gameLocal.entities[ collision.c.entityNum ];
	
	if ( collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) {
		ResetFlag( powerup );
	}
	return false;
}
// RAVEN END




const idEventDef EV_ResetSpawn ( "<resetspawn>" );
CLASS_DECLARATION( idItemPowerup, riDeadZonePowerup )
	EVENT( EV_ResetSpawn,		riDeadZonePowerup::Event_ResetSpawn )
END_CLASS

/*
================
riDeadZonePowerup::idItemPowerup
================
*/
riDeadZonePowerup::riDeadZonePowerup() {
}

/*
================
riDeadZonePowerup::Save
================
*/
void riDeadZonePowerup::Save( idSaveGame *savefile ) const {
}

/*
================
riDeadZonePowerup::Restore
================
*/
void riDeadZonePowerup::Restore( idRestoreGame *savefile ) {
}


/*
================
riDeadZonePowerup::Show
================
*/
void riDeadZonePowerup::Show()
{
	idItem::Show();
}


/*
================
riDeadZonePowerup::Spawn
================
*/
void riDeadZonePowerup::Spawn( void ) {
	powerup = POWERUP_DEADZONE;

	time = SEC2MS( gameLocal.serverInfo.GetInt("si_deadZonePowerupTime") );

	if ( spawnArgs.GetBool( "dropped" ) ) {
		time = SEC2MS( spawnArgs.GetInt( "time", "10" ) );
		if ( time > SEC2MS(10) )
			time = SEC2MS(10);

		Show();
		CancelEvents(&EV_Remove);
		PostEventSec( &EV_ResetSpawn, MS2SEC(time) );
	}
	else
		Hide();
}


/*
================
riDeadZonePowerup::Pickup
================
*/
bool riDeadZonePowerup::Pickup( idPlayer* player ) {
	// regular pickup routine, but unique items need to think to know when to respawn
	bool pickup;
	if ( player->PowerUpActive(POWERUP_DEADZONE) )
		return false;

	pickup = idItemPowerup::Pickup( player );

	if ( spawnArgs.GetBool( "dropped" ) ) {
		// Cancel the respawn so the powerup doesn't get removed 
		// from the player that just picked it up.
		PostEventMS( &EV_Remove, 0 );
		CancelEvents( &EV_ResetSpawn );
	}

	return pickup;
}

/*
================
riDeadZonePowerup::ResetSpawn
================
*/
void riDeadZonePowerup::ResetSpawn( int powerup ) {
	int count = 1;
	idEntity* ent;
	riDeadZonePowerup* spawnSpot = 0;
	for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {

		// If its not a DeadZone powerup then skip it
		if ( !ent->IsType( riDeadZonePowerup::Type ) ) {
			continue;			
		}
		
		// Make sure its the right type first
		riDeadZonePowerup* flag;
		flag = static_cast<riDeadZonePowerup*>(ent);
		if ( flag->powerup != powerup ) {
			continue;
		}
		
		if ( flag->spawnArgs.GetBool("dropped", "0") && flag == this ) {			
			flag->PostEventMS( &EV_Remove, 0 );
		} else {
			if ( !flag->IsVisible() ) {
				if ( !(rand()%count)  ) {
					spawnSpot = flag;
					if ( flag->spawnArgs.GetBool("dropped", "0") )
						gameLocal.DPrintf("WARNING: Trying to spawn a powerup at a DROPPED location!");
				}
				count++;
			}		
		}
	}	
	
	if ( spawnSpot ) {
		spawnSpot->Show();
		spawnSpot->PostEventMS( &EV_RespawnItem, 0 );			
	}
	else {
		gameLocal.DPrintf("WARNING: Failed to find a valid spawn spot!");
	}
}
  
/*
================
riDeadZonePowerup::Collide
================
*/
bool riDeadZonePowerup::Collide( const trace_t &collision, const idVec3 &velocity ) {
	idEntity* lol = gameLocal.entities[ collision.c.entityNum ];
	if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) {
		PostEventMS( &EV_ResetSpawn, 0 ); // Just respawn it.
	}
	return false;
}

/*
================
riDeadZonePowerup::Event_ResetFlag
================
*/
void riDeadZonePowerup::Event_ResetSpawn( void ) {
	ResetSpawn( POWERUP_DEADZONE );
}
