/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts 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 3 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, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/bullet.cpp                            $*
 *                                                                                             *
 *                      $Author:: Byon_g                                                      $*
 *                                                                                             *
 *                     $Modtime:: 3/13/02 5:22p                                               $*
 *                                                                                             *
 *                    $Revision:: 164                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "bullet.h"
#include "projectile.h"
#include "combat.h"	  // for collision groups (should be damage.h?)
#include "pscene.h"
#include "weaponmanager.h"
#include "assets.h"
#include "debug.h"
#include "armedgameobj.h"
#include "crandom.h"
#include "surfaceeffects.h"
#include "mesh.h"
#include "wwaudio.h"
#include "explosion.h"
#include "building.h"
#include "persistfactory.h"
#include "wwphysids.h"
#include "damageablestaticphys.h"
#include "wwprofile.h"
#include "part_emt.h"
#include "realcrc.h"
#include "soldier.h"
#include "segline.h"
#include "timeddecophys.h"
#include "simplegameobj.h"
#include "c4.h"


/*
** Constants for this module
*/
const float _INSTANT_BULLET_THRESHHOLD	= 400;  // If speed is >= _INSTANT_BULLET_THRESHHOLD m/s, treat it an an instant bullet


/**
** BeamEffectManagerClass
** This is a static object that simply manages the active beam effect.  Its job is to cache un-used beams
** so that we aren't allocating them at run time and to add new beam effects to the scene when needed.
*/
class BeamEffectManagerClass : public CombatPhysObserverClass
{
public:
	void		Init( void )		{}
	void		Shutdown( void );

	/*
	** Create a beam effect and put it in the scene.  Whenever possible, this function
	** tries to recycle a previously created beam effect
	*/
	void								Create_Beam_Effect(const AmmoDefinitionClass * ammo_def,const Vector3 & p0,const Vector3 & p1);


	/*
	** PhysObserver Interface - whenever a beam expires, we put it into a list so we can
	** recycle it next time we need a beam effect
	*/
	virtual void					Object_Removed_From_Scene(PhysClass * observed_obj);


private:

	void								Internal_Get_New_Beam(SegmentedLineClass ** beam_model,TimedDecorationPhysClass ** beam_wrapper);

	RefMultiListClass<TimedDecorationPhysClass>	UnusedBeamList;
};

static BeamEffectManagerClass _TheBeamEffectManager;


void	BeamEffectManagerClass::Shutdown( void )
{
	while (UnusedBeamList.Peek_Head()) {
		UnusedBeamList.Release_Head();
	}
}

void BeamEffectManagerClass::Create_Beam_Effect(const AmmoDefinitionClass * ammo_def,const Vector3 & p0,const Vector3 & p1)
{
	SegmentedLineClass * beam_model = NULL;
	TimedDecorationPhysClass * beam_wrapper = NULL;

	Internal_Get_New_Beam(&beam_model,&beam_wrapper);

	// line effect from "start" to "data.Position"
	static Vector3 points[2];
	points[0] = p0;
	points[1] = p1;

	const int BEAM_SUBDIVISION				= 4;
	const float BASE_AMPLITUDE				= 0.3f;
	const float AMPLITUDE_PER_METER		= 0.04f;

	/*
	** User controlled settings
	*/
	TextureClass * beam_texture = Get_Texture_From_Filename(ammo_def->BeamTexture);
	beam_model->Set_Texture(beam_texture);
	REF_PTR_RELEASE(beam_texture);

	beam_model->Set_Points(2,points);
	beam_model->Set_Width(ammo_def->BeamWidth);
	beam_model->Set_Color(ammo_def->BeamColor);

	beam_model->Set_UV_Offset_Rate(Vector2(0,1.0f / ammo_def->BeamTime));

	if (ammo_def->BeamSubdivisionEnabled) {
		beam_model->Set_Noise_Amplitude((BASE_AMPLITUDE + AMPLITUDE_PER_METER * (p1-p0).Quick_Length()) * ammo_def->BeamSubdivisionScale);
		beam_model->Set_Subdivision_Levels(BEAM_SUBDIVISION);
	} else {
		beam_model->Set_Noise_Amplitude(0.0f);
		beam_model->Set_Subdivision_Levels(0);
	}

	/*
	** Constants; I don't let the user control these for now
	*/
	beam_model->Set_Texture_Tile_Factor(1.0f); // Only relevant for TILED_TEXTURE_MAP mode
	beam_model->Set_Texture_Mapping_Mode(SegLineRendererClass::UNIFORM_WIDTH_TEXTURE_MAP);
	beam_model->Set_Opacity(1.0f);
	beam_model->Set_Shader(ShaderClass::_PresetAdditiveShader);
	beam_model->Set_Merge_Abort_Factor(1.0f);
	beam_model->Set_Merge_Intersections(false);
	beam_model->Set_Disable_Sorting(false);
	beam_model->Set_Freeze_Random(ammo_def->BeamSubdivisionFrozen);
	beam_model->Set_End_Caps(ammo_def->BeamEndCaps);
	beam_model->Reset_Line();

	beam_wrapper->Set_Lifetime(ammo_def->BeamTime);
	beam_wrapper->Set_Transform(Matrix3D(1));
	beam_wrapper->Enable_Is_Pre_Lit(true);

	COMBAT_SCENE->Add_Dynamic_Object(beam_wrapper);

	REF_PTR_RELEASE(beam_wrapper);
	REF_PTR_RELEASE(beam_model);
	REF_PTR_RELEASE(beam_texture);
}

void BeamEffectManagerClass::Object_Removed_From_Scene(PhysClass * observed_obj)
{
	/*
	** One of the "beams" that we are observing expired and was removed from the scene so
	** lets put it in the list for future re-use
	*/
	UnusedBeamList.Add((TimedDecorationPhysClass *)observed_obj);
}

void BeamEffectManagerClass::Internal_Get_New_Beam(SegmentedLineClass ** set_beam_model,TimedDecorationPhysClass ** set_beam_wrapper)
{
	if (UnusedBeamList.Is_Empty()) {
		SegmentedLineClass * beam_model = NEW_REF(SegmentedLineClass,());

		TimedDecorationPhysClass * beam_wrapper = NEW_REF(TimedDecorationPhysClass,());
		beam_wrapper->Enable_Dont_Save(true);
		beam_wrapper->Set_Collision_Group(UNCOLLIDEABLE_GROUP);
		beam_wrapper->Set_Model(beam_model);
		beam_wrapper->Set_Observer(this);
		*set_beam_model = beam_model;
		*set_beam_wrapper = beam_wrapper;	// refs returned to the caller

	} else {

		*set_beam_wrapper = UnusedBeamList.Remove_Head(); // refs returned to the caller
		*set_beam_model = (SegmentedLineClass *)((*set_beam_wrapper)->Get_Model());

	}
}



/*
** Core Bullet Data for simulation
*/
class BulletDataClass {
public:
	BulletDataClass(  const AmmoDefinitionClass * def = NULL, const ArmedGameObj * owner = NULL,
							const Vector3 & position = Vector3(0,0,0), const Vector3 & velocity = Vector3(0,0,0) ) :
		AmmoDefinition( def ),
		Owner( owner ),
		Position( position ),
		Velocity( velocity ),
		SoftPierceCount( 0 ),
		Destroy( false ),
		DidExplode( false ),
		LastHitID( 0 ),
		GrenadeSafetyTimer( 0 )
	{}

	ArmedGameObj * Get_Owner( void ) const 	{ return (ArmedGameObj *)Owner.Get_Ptr(); }

	CollisionReactionType	Bullet_Collision_Occurred( const CollisionEventClass & event );
	ExpirationReactionType	Bullet_Expired();

	const AmmoDefinitionClass	*AmmoDefinition;
	GameObjReference				Owner;
	Vector3							Position;
	Vector3							Velocity;
	int								SoftPierceCount;
	bool							Destroy;
	bool							DidExplode;
	int								LastHitID;
	float							GrenadeSafetyTimer;
};

/*
** Bullet_Collision_Occured
*/
CollisionReactionType BulletDataClass::Bullet_Collision_Occurred( const CollisionEventClass & event )
{
	WWPROFILE( "Bullet Collision Occurred" );

	WWASSERT(event.OtherObj);
	WWASSERT(event.CollisionResult != NULL);
//	WWDEBUG_SAY(( "Bullet Collision\n" ));

	//
	// Pre-calculate some useful variables
	//
	CollisionReactionType return_value = COLLISION_REACTION_DEFAULT;
	OffenseObjectClass offense( AmmoDefinition->Damage, (int)AmmoDefinition->Warhead, Get_Owner() );
	offense.EnableClientDamage = true;	// Only bullets apply to client damage;

	const Vector3 & collision_point = event.CollisionResult->ContactPoint;
	const Vector3 & collision_normal = event.CollisionResult->Normal;

	PhysicalGameObj * other = NULL;
	BuildingGameObj * building = NULL;
	if ( event.OtherObj->Get_Observer() != NULL ) {
		other = ((CombatPhysObserverClass *)event.OtherObj->Get_Observer())->As_PhysicalGameObj();
		building = ((CombatPhysObserverClass *)event.OtherObj->Get_Observer())->As_BuildingGameObj();
	}

	//
	// If the bullet hits its owner or its owner's vehicle, allow it to continue
	// Also, if it is a simple "Hidden Object", allow the bullet to continue
	//
	if ( other != NULL && Get_Owner() != NULL ) {

		SimpleGameObj * simple = other->As_SimpleGameObj();
		VehicleGameObj * vehicle = other->As_VehicleGameObj();
		SoldierGameObj * soldier = Get_Owner()->As_SoldierGameObj();

		if ( vehicle != NULL && soldier != NULL && vehicle == soldier->Get_Vehicle() ) {
//			Debug_Say(( "Bullet Hitting its owner's vehicle\n" ));
			return COLLISION_REACTION_NO_BOUNCE;
		}
		if ( other == Get_Owner() ) {
//			Debug_Say(( "Bullet Hitting its owner\n" ));
			return COLLISION_REACTION_NO_BOUNCE;
		}
		if ((simple != NULL) && (simple->Is_Hidden_Object())) {
			return COLLISION_REACTION_NO_BOUNCE;
		}
	}

	//
	// Apply Graphical Effects.
	// If the mesh is shatterable it is shattered,  Apply a surface effect depending on the surface type
	// that was collided with.  In the case that the mesh was shattered, we disable surface effect decals.
	//
	bool shattered = false;
	if ((event.CollidedRenderObj != NULL) && (event.CollidedRenderObj->Class_ID() == RenderObjClass::CLASSID_MESH)) {
		MeshClass * mesh = (MeshClass *)event.CollidedRenderObj;

		if ((mesh->Get_W3D_Flags() & W3D_MESH_FLAG_SHATTERABLE) && (mesh->Is_Not_Hidden_At_All())) {

			PhysicsSceneClass::Get_Instance()->Shatter_Mesh(	mesh,
																				collision_point,
																				collision_normal,
																				Velocity	);

			// remeber that we shattered the mesh so that we don't create decals later
			shattered = true;

			// Bullets always pass through shattered objects because we sometimes have two-layered
			// windows that have different materials on the two sides.  We don't want bullets to ever
			// shatter only one "side" of a window!
			return_value = COLLISION_REACTION_NO_BOUNCE;
		}
	}

	// Ignore backfaces (after shattering for windows)
	float dot = Vector3::Dot_Product( event.CollisionResult->Normal, Velocity );
	if ( dot > 0 ) {
		return COLLISION_REACTION_NO_BOUNCE;
	}

	//
	// Apply Damage
	// There are several types of objects that can be damaged: normal dynamic game objects, damageable static
	// physics objects, and buildings.  A building is damaged whenever any of its meshes or aggregates are
	// hit by a bullet.
	// Also, we only apply damage if the mesh wasn't shattered.  Bullets pretend like nothing happened
	// if they shatter something.
	//
	if (!shattered) {

		const char * collided_obj_name = NULL;
		if ( event.CollidedRenderObj != NULL ) {
			collided_obj_name = event.CollidedRenderObj->Get_Name();
		}

		if ( other ) {	// if hitting a game object,

			if ( other->Get_ID() == LastHitID ) {
//				Debug_Say(( "Double Hit\n" ));
				return COLLISION_REACTION_NO_BOUNCE;
			}

			if ( GrenadeSafetyTimer > 0 ) {
				// only if the other is not an enemy
				if ( other && Get_Owner() && !other->Is_Enemy( Get_Owner() ) ) {
					return COLLISION_REACTION_DEFAULT;		// Just bounce
				}
			}

			LastHitID = other->Get_ID();

			// Apply Bullet Damage to a game object
			// Allow it to happen for clients, so we can get the repair effect
//			if ( CombatManager::I_Am_Server() ) {
				other->Apply_Damage_Extended( offense, 1.0f, Velocity, collided_obj_name );

				// If the bullet is hitting C4, also apply damage to the object the C4 is
				// attached to.  This is a fix for the "C4 armor" exploit.
				if (other->As_C4GameObj() != NULL) {
					ScriptableGameObj * host = other->As_C4GameObj()->Get_Stuck_Object();
					if ((host != NULL) && (host->As_PhysicalGameObj() != NULL)) {
						host->As_PhysicalGameObj()->Apply_Damage_Extended( offense, 1.0f, Velocity, NULL );
					}
				}
//			}

			if ( ( !other->Is_Soft() ) ||
				  ( ++SoftPierceCount >= AmmoDefinition->SoftPierceLimit ) ) {

				if ( AmmoDefinition->ExplosionDefID ) {
					ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, collision_point, Get_Owner(), -collision_normal, other );
				}

				Destroy = true;
				return_value = COLLISION_REACTION_STOP_MOTION;
			} else {
				return_value = COLLISION_REACTION_NO_BOUNCE;
			}

		} else {		// if hitting terain

			if ( GrenadeSafetyTimer > 0 ) {
				Debug_Say(( "Safety\n" ));
				return COLLISION_REACTION_DEFAULT;		// Just bounce
			}

			// If hitting a building, apply damage to it
			if ( building != NULL ) { //	&& CombatManager::I_Am_Server() ) {
				building->Apply_Damage_Building( offense, event.OtherObj->As_StaticPhysClass() );
			}

			// Check for damage to a DamageableStaticPhys object
			// For now I'm using the persist factory chunk-ID for RTTI... If a better solution
			// turns up we should change this.
			if (	event.OtherObj->Get_Factory().Chunk_ID() == PHYSICS_CHUNKID_DAMAGEABLESTATICPHYS 
//					&& CombatManager::I_Am_Server() 
				) 
			{
				DamageableStaticPhysClass * damphys = (DamageableStaticPhysClass *)event.OtherObj;
				OffenseObjectClass offense( AmmoDefinition->Damage, (int)AmmoDefinition->Warhead, Get_Owner() );
				offense.EnableClientDamage = true;	// Only bullets apply to client damage;
				damphys->Apply_Damage_Static( offense );
			}

			// check for a non-stopping surface
			if ( !SurfaceEffectsManager::Does_Surface_Stop_Bullets( event.CollisionResult->SurfaceType ) ) {
				return_value = COLLISION_REACTION_NO_BOUNCE;
			} else {

				// If the ammo explodes when it hits terrain, then create an explosion and kill the bullet
				if ( AmmoDefinition->TerrainActivated ) {

					if ( AmmoDefinition->ExplosionDefID ) {
						ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, collision_point, Get_Owner(), -collision_normal, building );
						DidExplode = true;
					}

					Destroy = true;
					return_value = COLLISION_REACTION_STOP_MOTION;
				}
			}
		}
	}

	// Only make a bullet hit if it doesn't make an explosion
	if ( !DidExplode ) {
		Matrix3D surface_tm;
		surface_tm.Look_At(collision_point,collision_point-collision_normal,FreeRandom.Get_Float(0,DEG_TO_RADF(360)));
		int my_type = AmmoDefinition->HitterType;
		bool allow_decals = (shattered == false);
		if ( (float)AmmoDefinition->Damage < 0.0f ) {
			allow_decals = false;
		}
		SurfaceEffectsManager::Apply_Effect( event.CollisionResult->SurfaceType, my_type, surface_tm, event.OtherObj, Get_Owner(), allow_decals );
	}

	return return_value;
}

ExpirationReactionType	BulletDataClass::Bullet_Expired( void )
{

	Destroy = true;

	if ( AmmoDefinition->TimeActivated && AmmoDefinition->ExplosionDefID && !DidExplode ) {
		ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, Position, Get_Owner() );
	}

	return EXPIRATION_DENIED; // Don't let it die, I'll pull it from the scene later
}


/*
** BulletClass (for non-instant bullets)
*/
class	BulletClass : public CombatPhysObserverClass, public MultiListObjectClass, public PostLoadableClass {

public:
	~BulletClass( void );

	void		Init( const BulletDataClass & data, float progress_time, const Vector3 & target, DamageableGameObj * target_object = NULL );
	void		Shutdown( void );
	void		Think( void );

	// Save / Load
	virtual	bool	Save( ChunkSaveClass & csave );
	virtual	bool	Load( ChunkLoadClass & cload );
	virtual	void	On_Post_Load(void);
	bool	Is_Valid( void )	{ return BulletData.AmmoDefinition != NULL; }

	// Collision
	virtual CollisionReactionType		Collision_Occurred( const CollisionEventClass & event );
	virtual ExpirationReactionType	Object_Expired( PhysClass * observed_obj );

private:
	BulletDataClass				BulletData;
	ProjectileClass *				Projectile;			//	for physics
	Vector3							TargetVector;
	GameObjReference				TargetObject;
	float								TrackingErrorTimer;
	Vector3							TrackingError;
	long								ModelNameCRC;

	BulletClass( void );

	friend	class		BulletManager;

};


BulletClass::BulletClass( void ) :
	Projectile( NULL ),
	TargetVector( 0,0,0 ),
	TrackingErrorTimer( 0 ),
	TrackingError( 0,0,0 ),
	ModelNameCRC( 0 )
{
	WWASSERT( Projectile == NULL );
	Projectile = NEW_REF( ProjectileClass, () );
   Projectile->Set_Collision_Group( BULLET_COLLISION_GROUP );
}

BulletClass::~BulletClass( void )
{
	if ( Projectile != NULL ) {
		Projectile->Set_Observer(NULL);
		Projectile->Release_Ref();
		Projectile = NULL;
	}
}

void BulletClass::Init( const BulletDataClass & data, float progress_time, const Vector3 & target, DamageableGameObj * target_object )
{
	WWPROFILE( "Bullet Init" );

	BulletData.AmmoDefinition = data.AmmoDefinition;
	BulletData.Owner = data.Get_Owner();
	BulletData.SoftPierceCount = data.SoftPierceCount;
	BulletData.Destroy = data.Destroy;
	BulletData.LastHitID = 0;			// We haven't hit anyone yet.
	BulletData.DidExplode = false;
	BulletData.GrenadeSafetyTimer = data.GrenadeSafetyTimer;

	if ( data.AmmoDefinition ) {
		BulletData.GrenadeSafetyTimer = data.AmmoDefinition->GrenadeSafetyTime;
	}

	TargetVector = target;
	TargetObject = target_object;
	TrackingErrorTimer = 0;
	TrackingError = Vector3( 0,0,0 );

	Projectile->Set_Observer( this );
	Projectile->Set_Bounce_Count( BulletData.AmmoDefinition->MaxBounces );
	if ( BulletData.AmmoDefinition ) {
		Projectile->Set_Gravity_Multiplier( BulletData.AmmoDefinition->Gravity );
		Projectile->Set_Elasticity( BulletData.AmmoDefinition->Elasticity );
	}
	Projectile->Enable_Is_Pre_Lit(true);	// bullets don't get lighting.

	if ( Projectile->Peek_Model() != NULL &&
		  Projectile->Peek_Model()->Get_Name() != NULL &&
		  BulletData.AmmoDefinition->ModelName.Compare_No_Case( Projectile->Peek_Model()->Get_Name() ) == 0 ) {
		// We don't need to do anything
	} else {
		RenderObjClass * model = NULL;
		model = WW3DAssetManager::Get_Instance ()->Create_Render_Obj( BulletData.AmmoDefinition->ModelName );

		// If no name is given, lets create the NULL render obj
		if ( model == NULL ) {
			Debug_Say(( "Bullet Not Found \"%s\" \n", BulletData.AmmoDefinition->ModelName ));
			model = WW3DAssetManager::Get_Instance ()->Create_Render_Obj( "NULL" );

		}

		Projectile->Set_Model( model );

		if (model) {
			if ( BulletData.AmmoDefinition->ModelName.Compare_No_Case( model->Get_Name() ) != 0 ) {
				Debug_Say(( "Possible bullet twiddler!!  %s %s\n", BulletData.AmmoDefinition->ModelName, model->Get_Name() ));
			}
//			ModelNameCRC = CRC_Stringi( model->Get_Name() );
			ModelNameCRC = CRC_Stringi( BulletData.AmmoDefinition->ModelName );
			model->Release_Ref();
		}
		else {
			ModelNameCRC = 0xFFFFFFFF;
		}

	}

	if (Projectile->Get_Gravity_Multiplier() < 0.1f) {
		Projectile->Set_Orientation_Mode_Aligned_Fixed();
	} else {
		Projectile->Set_Orientation_Mode_Aligned();
	}

	COMBAT_SCENE->Add_Dynamic_Object( Projectile );

	float duration = (float) BulletData.AmmoDefinition->Range / (float) BulletData.AmmoDefinition->Velocity;
	if (duration <= 0.0) {
		Debug_Say(( "NULL DURATION\n" ));
	}
	Projectile->Set_Lifetime( duration * 1.2f );
	Projectile->Set_Position( data.Position );
	WWASSERT(data.Velocity.Is_Valid());
	Projectile->Set_Velocity( data.Velocity );

	// If there are any emitters in this model, reset them for our new position
	RenderObjClass * model = Projectile->Peek_Model();
	model->Restart();

	// Timestep last, incase it gets shutdown during this call
	if ( data.Get_Owner() != NULL ) {
		data.Get_Owner()->Peek_Physical_Object()->Inc_Ignore_Counter();
	}
	Projectile->Timestep( progress_time );
	if ( data.Get_Owner() != NULL ) {
		data.Get_Owner()->Peek_Physical_Object()->Dec_Ignore_Counter();
	}

}

void	BulletClass::Shutdown( void )
{
	if ( Projectile != NULL ) {
		COMBAT_SCENE->Remove_Object( Projectile );
		Projectile->Set_Observer( NULL );
	}

	BulletData.AmmoDefinition = NULL;
	BulletData.Owner = NULL;
}

/*
** BulletClass Save and Load
*/
enum	{
	CHUNKID_VARIABLES						=	910991544,
	CHUNKID_OWNER,
	CHUNKID_TARGET_OBJECT,

	MICROCHUNKID_AMMO_DEFINITION_ID	=	1,
	MICROCHUNKID_PROJECTILE,
	MICROCHUNKID_SOFT_PIERCE_COUNT,
	MICROCHUNKID_DESTROY,
	MICROCHUNKID_TARGET_VECTOR,
	MICROCHUNKID_TRACKING_ERROR_TIMER,
	MICROCHUNKID_TRACKING_ERROR,
	MICROCHUNKID_MODEL_NAME_CRC,
};

bool	BulletClass::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_VARIABLES );
		WWASSERT( BulletData.AmmoDefinition != NULL );
		int def_id = BulletData.AmmoDefinition->Get_ID();
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_AMMO_DEFINITION_ID, def_id );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PROJECTILE, Projectile );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SOFT_PIERCE_COUNT, BulletData.SoftPierceCount );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DESTROY, BulletData.Destroy );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TARGET_VECTOR, TargetVector );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TRACKING_ERROR_TIMER, TrackingErrorTimer );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TRACKING_ERROR, TrackingError );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MODEL_NAME_CRC, ModelNameCRC );
	csave.End_Chunk();

	if ( BulletData.Get_Owner() != NULL ) {
		csave.Begin_Chunk( CHUNKID_OWNER );
		BulletData.Owner.Save( csave );
		csave.End_Chunk();
	}

	if ( TargetObject.Get_Ptr() != NULL ) {
		csave.Begin_Chunk( CHUNKID_TARGET_OBJECT );
		TargetObject.Save( csave );
		csave.End_Chunk();
	}

	return true;
}

bool	BulletClass::Load( ChunkLoadClass & cload )
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_VARIABLES:
			{
				int def_id = 0;
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_AMMO_DEFINITION_ID, def_id );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_PROJECTILE, Projectile );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_SOFT_PIERCE_COUNT, BulletData.SoftPierceCount );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DESTROY, BulletData.Destroy );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TARGET_VECTOR, TargetVector );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TRACKING_ERROR_TIMER, TrackingErrorTimer );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TRACKING_ERROR, TrackingError );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MODEL_NAME_CRC, ModelNameCRC );

						default:
							Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
							break;
					}
					cload.Close_Micro_Chunk();
				}

				WWASSERT( BulletData.AmmoDefinition == NULL );
				BulletData.AmmoDefinition = WeaponManager::Find_Ammo_Definition( def_id );
				WWASSERT( BulletData.AmmoDefinition != NULL );

				WWASSERT( Projectile != NULL );
				if ( Projectile != NULL ) {
					REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&Projectile );
				}

				break;
			}

			case CHUNKID_OWNER:
				BulletData.Owner.Load( cload );
				break;

			case CHUNKID_TARGET_OBJECT:
				TargetObject.Load( cload );
				break;

			default:
				Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
				break;

		}
		cload.Close_Chunk();
	}

	SaveLoadSystemClass::Register_Post_Load_Callback(this);

	return true;
}

void BulletClass::On_Post_Load (void)
{
	// Plug ourselves back into the physics object as an observer
	WWASSERT(Projectile != NULL);
	Projectile->Set_Observer(this);
	return ;
}


CollisionReactionType BulletClass::Collision_Occurred( const CollisionEventClass & event )
{
	// Copy the data from the Projectile
	Projectile->Get_Velocity( &BulletData.Velocity );
	Projectile->Get_Position( &BulletData.Position );
	CollisionReactionType result = BulletData.Bullet_Collision_Occurred( event );
	if ( BulletData.Destroy ) {
		Shutdown();
	}
	return result;
}

ExpirationReactionType	BulletClass::Object_Expired(PhysClass * observed_obj)
{
	// Copy the data from the Projectile
	Projectile->Get_Velocity( &BulletData.Velocity );
	Projectile->Get_Position( &BulletData.Position );
	ExpirationReactionType result = BulletData.Bullet_Expired();
	if ( BulletData.Destroy ) {
		Shutdown();
	}
	return result;
}


#define	RANDOM_VECTOR( spread )		Vector3( FreeRandom.Get_Float( -(spread), (spread) ),	\
															FreeRandom.Get_Float( -(spread), (spread) ),	\
															FreeRandom.Get_Float( 0, (spread) ) )

void	BulletClass::Think( void )
{
	WWPROFILE( "Bullet Think" );

	// Count down safety timer
	BulletData.GrenadeSafetyTimer -= TimeManager::Get_Frame_Seconds();
	if ( BulletData.GrenadeSafetyTimer < 0 ) {
		BulletData.GrenadeSafetyTimer = 0;
	}

	// Handle Homing Bullets
	if ( BulletData.AmmoDefinition->IsTracking ) {

		// Track your target object
		DamageableGameObj * target_obj = (DamageableGameObj *)TargetObject.Get_Ptr();
		if ( target_obj != NULL ) {
			if ( target_obj->As_PhysicalGameObj() ) {
				TargetVector = target_obj->As_PhysicalGameObj()->Get_Bullseye_Position();
			} else {
				target_obj->Get_Position( &TargetVector );
			}
		}

		Vector3	target_vector = TargetVector;

		Vector3	obj_pos;
		Projectile->Get_Transform().Get_Translation( &obj_pos );
//			Debug_Say(( "Bullet Tracking %f %f %f\n", obj_pos.X, obj_pos.Y, obj_pos.Z ));
		target_vector -= obj_pos;

		// Find distance to target
		float target_distance = target_vector.Length();

		TrackingErrorTimer -= TimeManager::Get_Frame_Seconds();
		if ( TrackingErrorTimer < 0 ) {
			TrackingErrorTimer = FreeRandom.Get_Float( 0.1f, 0.4f );
			TrackingError = RANDOM_VECTOR( BulletData.AmmoDefinition->RandomTrackingScale * target_distance / 2 );
		}
		target_vector += TrackingError;
		target_vector.Normalize();

		Vector3	current_vector;
		Projectile->Get_Velocity( &current_vector );
		current_vector.Normalize();

		Vector3 cross = Vector3::Cross_Product( current_vector, target_vector );

		// only re-aim if cross product not near zero
		if ( cross.Length() > WWMATH_EPSILON ) {

			float dot = Vector3::Dot_Product( target_vector, current_vector );

			if ( WWMath::Fabs(dot) >= 1.0f ) {
				// No turning needed
			} else {

				float angle = WWMath::Fast_Acos( dot );
				float	allowed_turn = BulletData.AmmoDefinition->TurnRate * TimeManager::Get_Frame_Seconds();

				if ( angle > allowed_turn ) {
					angle = allowed_turn;
				}

				cross.Normalize();
				Matrix3D tm(cross,angle);
				Vector3	current_vector;
				Projectile->Get_Velocity( &current_vector );
				WWASSERT(current_vector.Is_Valid());
				current_vector = tm.Rotate_Vector( current_vector );
				WWASSERT(current_vector.Is_Valid());
				Projectile->Set_Velocity( current_vector );
			}
		}
	}
}

/*
** Instant Bullet Code
*/
void	Simulate_Instant_Bullet( BulletDataClass & data, float progress_time )
{
	WWPROFILE("Simulate_Instant_Bullet");
//	WWASSERT(data.Position.Is_Valid());
//	WWASSERT(data.Velocity.Is_Valid());

	Vector3	start = data.Position;
	Vector3	end = data.Velocity;
	end.Normalize();
	end = start + (end * data.AmmoDefinition->Range);

	// This is that last object we choose to ignore
	PhysClass	*blocker = NULL;

	// Always ignore the owner
	if ( data.Get_Owner() != NULL ) {
		if ( data.Get_Owner()->Peek_Physical_Object() != NULL ) {
			data.Get_Owner()->Peek_Physical_Object()->Inc_Ignore_Counter();
		}
	}

	bool	done = false;
	int hit_counter = 0;
	const int MAX_HITS = 5;

	while ( !done && (hit_counter < MAX_HITS)) {

		hit_counter++;

		// Check for collisions in the path of the object
		CastResultStruct res;
		LineSegClass ray( data.Position, end );
		PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);
{WWPROFILE("Cast_Ray");
		PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);
}

		// If the result was a "startbad", we are done
		if (raytest.Result->StartBad) {
//			Debug_Say(( "Simulate_Instant_Bullet Startbad\n" ));
			done = true;
		} else {

			if (raytest.Result->Fraction < 1.0f) {
				// If there was a collision, set us at the point of collision
				// Also, fill in the cast result struct.
				ray.Compute_Point(raytest.Result->Fraction,&(raytest.Result->ContactPoint));
				data.Position = raytest.Result->ContactPoint;

				// Notify the parties involved
				WWASSERT(raytest.CollidedPhysObj != NULL);

				CollisionReactionType reaction = COLLISION_REACTION_DEFAULT;

				CollisionEventClass event;
				event.CollisionResult = raytest.Result;
				event.CollidedRenderObj = raytest.CollidedRenderObj;

				event.OtherObj = raytest.CollidedPhysObj;
				reaction |= data.Bullet_Collision_Occurred(event);

				if ( reaction & COLLISION_REACTION_NO_BOUNCE ) {
					// We were requested to fly through.  Mark the current blocker as ignore
					// so we collide with him no more this pass
					if ( blocker ) {	 // Stop ignoring the last blocker
						blocker->Dec_Ignore_Counter();
					}
					blocker = raytest.CollidedPhysObj;
					if ( blocker ) {	// Start ignoring this blocker
						blocker->Inc_Ignore_Counter();
					}
				} else {
					done = true;
				}
			} else {
				done = true;
				data.Position = end;
			}
		}
	}

	if ( blocker ) {	// Stop ignoring the last blocker
		blocker->Dec_Ignore_Counter();
	}

	// stop ignoring the owner
	if ( data.Get_Owner() != NULL ) {
		if ( data.Get_Owner()->Peek_Physical_Object() != NULL ) {
			data.Get_Owner()->Peek_Physical_Object()->Dec_Ignore_Counter();
		}
	}

	// If not destroyed, Expire..
	if ( !data.Destroy ) {
		data.Bullet_Expired();
	}

	// If the definition requests a beam effect, launch it
	if (data.AmmoDefinition->BeamEnabled) {
		_TheBeamEffectManager.Create_Beam_Effect(data.AmmoDefinition,start,data.Position);
	}
}

/*
** BulletManager
*/
MultiListClass<BulletClass>	LiveBulletList;
MultiListClass<BulletClass>	DeadBulletList;

void	BulletManager::Init( void )
{
	_TheBeamEffectManager.Init();
}

void	BulletManager::Shutdown( void )
{
	while ( !LiveBulletList.Is_Empty() ) {
		delete LiveBulletList.Remove_Head();
	}
	while ( !DeadBulletList.Is_Empty() ) {
		delete DeadBulletList.Remove_Head();
	}

	_TheBeamEffectManager.Shutdown();
}

void	BulletManager::Update( void )
{
	MultiListIterator<BulletClass> it( &LiveBulletList );;
	while (!it.Is_Done()) {
		BulletClass * bullet = it.Peek_Obj();
		if (bullet->BulletData.Destroy) {
			bullet->Shutdown();
			it.Remove_Current_Object();
			DeadBulletList.Add( bullet );
		} else {
			bullet->Think();
			it.Next();
		}
	}
}

#define	BULLET_SPEED_CHEAT	0

void	BulletManager::Create_Bullet( const AmmoDefinitionClass * def, const Vector3 & position,
	const Vector3 & velocity, const ArmedGameObj * owner, float progress_time, const Vector3 & target,
	DamageableGameObj * target_obj )
{
	WWASSERT(velocity.Is_Valid());

	BulletDataClass data( def, owner, position, velocity );

#if BULLET_SPEED_CHEAT 
	if ( (float)def->Velocity >= _INSTANT_BULLET_THRESHHOLD/3 ) {
#else
	if ( (float)def->Velocity >= _INSTANT_BULLET_THRESHHOLD ) {
#endif
		Simulate_Instant_Bullet( data, progress_time );
		return;
	}

	WWPROFILE( "Create Bullet" );
	BulletClass * bullet = NULL;

	// Find a bullet
	long crc = CRC_Stringi( def->ModelName );
	MultiListIterator<BulletClass> it( &DeadBulletList );;
	while ( !it.Is_Done() && bullet == NULL ) {
		BulletClass * test_bullet = it.Peek_Obj();
		if ( test_bullet->ModelNameCRC == crc ) {
			bullet = test_bullet;
			it.Remove_Current_Object();
		} else {
			it.Next();
		}
	}

	if ( bullet == NULL ) {
		bullet = new BulletClass();
	}

	if ( bullet != NULL ) {
		bullet->Init( data, progress_time, target, target_obj );
		LiveBulletList.Add( bullet );
	}
}

/*
** Save & Load
*/
enum	{
	CHUNKID_BULLET								=	916991653,
};

bool	BulletManager::Save( ChunkSaveClass &csave )
{
	MultiListIterator<BulletClass> it( &LiveBulletList );;
	while (!it.Is_Done()) {
		BulletClass * bullet = it.Peek_Obj();

		// Only save valid bullets
		if ( bullet->Is_Valid() ) {
			csave.Begin_Chunk( CHUNKID_BULLET );
			bullet->Save( csave );
			csave.End_Chunk();
		}
		it.Next();
	}
	return true;
}

bool	BulletManager::Load( ChunkLoadClass &cload )
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_BULLET:
			{
				BulletClass * bullet = new BulletClass();
				bullet->Load( cload );
				LiveBulletList.Add( bullet );
				break;
			}

			default:
				Debug_Say(( "Unrecognized BulletManager chunkID %d\n", cload.Cur_Chunk_ID() ));
				break;
		}
		cload.Close_Chunk();
	}
	return true;
}

