/*
**	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/weapons.cpp                           $*
 *                                                                                             *
 *                      $Author:: Byon_g                                                      $*
 *                                                                                             *
 *                     $Modtime:: 3/27/02 4:43p                                               $*
 *                                                                                             *
 *                    $Revision:: 270                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


#include "weapons.h"
#include "weaponmanager.h"
#include "debug.h"
#include "soldier.h"
#include "combat.h"
#include "gameobjmanager.h"
#include "line3d.h"
#include "physcoltest.h"
#include "pscene.h"
#include "rendobj.h"
#include "quat.h"
#include "combatsound.h"
#include "slnode.h"
#include "scripts.h"
#include "WWAudio.H"
#include "Sound3D.H"
#include "chunkio.h"
#include "assets.h"
#include "bullet.h"
#include "crandom.h"
#include "damage.h"
#include "ccamera.h"
#include "rbody.h"
#include "timeddecophys.h"
#include "wwphysids.h"
#include "part_emt.h"
#include "vehicle.h"
#include "c4.h"
#include "objlibrary.h"
#include "wwprofile.h"
#include "hudinfo.h"
#include "projectile.h"
#include "combatphysobserver.h"
#include "surfaceeffects.h"
#include "beacongameobj.h"
#include "weaponview.h"
#include "diaglog.h"
#include "playerdata.h"
#include "cheatmgr.h"


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


/**
** EjectCasingObserverClass
** This is a class which observes all eject casings and creates sound effects when they
** collide.  Since the task of this observer is so simple, I am creating a single static
** instance of it and pluging it into all eject casings.
*/
class EjectCasingObserverClass : public CombatPhysObserverClass
{
public:
	virtual CollisionReactionType		Collision_Occurred(const CollisionEventClass & event)
	{
/*
		SurfaceEffectsManager::Apply_Effect(event.CollisionResult->SurfaceType,
														SurfaceEffectsManager::HITTER_TYPE_EJECT_CASING,
														Matrix3D(event.CollisionResult->ContactPoint),
														event.OtherObj, NULL, false);
*/
		return COLLISION_REACTION_DEFAULT;
	}
};


static EjectCasingObserverClass _TheEjectCasingObserver;






/*
**
*/
WeaponClass::WeaponClass( const WeaponDefinitionClass *def ) :
	Definition( NULL ),
	PrimaryAmmoDefinition( NULL ),
	SecondaryAmmoDefinition( NULL ),
	Model( NULL ),
	State( STATE_IDLE ),
	StateTimer( 0.0f ),
	UpdateModel( WEAPON_MODEL_UPDATE_IS_NEEDED ),
	NextAnimState( WEAPON_ANIM_NOT_FIRING ),
	CurrentAnimState( WEAPON_ANIM_NOT_FIRING ),
	LastFrameIsPrimaryTriggered( false ),
	LastFrameIsSecondaryTriggered( false ),
	IsPrimaryTriggered( false ),
	IsSecondaryTriggered( false ),
	TotalRoundsFired( 0 ),
	ClipRounds( 0 ),
	InventoryRounds( 0 ),
	BurstDelayTimer( 0 ),
	BurstCount( 0 ),
	BulletBumpTime( 0 ),
   DidFire( false ),
	ContinuousEmitters( NULL ),
	ContinuousSound( NULL ),
	C4DetonationMode( 1 ),
	Target( 0, 0, 0 ),
	FiringSound( NULL ),
	FiringSoundDefID( 0 ),
	WeaponExists( true ),
	SafetySet( false ),
	LockTriggers( false ),
	EmptySoundTimer( 0 )
{
	if ( def != NULL ) {
		Init( def );
	}
}

WeaponClass::~WeaponClass( void )
{
	if (FiringSound != NULL) {
		FiringSound->Remove_From_Scene ();
		FiringSoundDefID = 0;
	}

	REF_PTR_RELEASE( Model );
	REF_PTR_RELEASE( FiringSound );

	Do_Continuous_Effects( false );
}

void	WeaponClass::Init( const WeaponDefinitionClass *weapon_def )
{
	WWASSERT( Definition == NULL );
	Definition = weapon_def;
	WWASSERT( Definition != NULL );

	// Setup the ammo defs
	WWASSERT( Definition->PrimaryAmmoDefID != 0 );
	PrimaryAmmoDefinition = WeaponManager::Find_Ammo_Definition( Definition->PrimaryAmmoDefID );

	WWASSERT( Definition->SecondaryAmmoDefID != 0 );
	SecondaryAmmoDefinition = WeaponManager::Find_Ammo_Definition( Definition->SecondaryAmmoDefID );
}

/*
** Weapon Save and Load
*/

enum	{
	CHUNKID_VARIABLES						=	910991544,
	XXX_CHUNKID_AMMO,
	CHUNKID_OWNER_REF,
	XXXCHUNKID_TARGET_REF,
	XXXCHUNKID_DESIRED_TARGET_REF,
	CHUNKID_TARGET_OBJECT,

	MICROCHUNKID_IN_FLY_MODE			=	1,
	MICROCHUNKID_STATE,
	MICROCHUNKID_STATE_TIMER,
	MICROCHUNKID_IS_PRIMARY_TRIGGERED,
	XXXMICROCHUNKID_IS_SNIPING,
	MICROCHUNKID_ROUNDS_FIRED,
	MICROCHUNKID_NEXT_ANIM_STATE,
	MICROCHUNKID_CURR_ANIM_STATE,
	MICROCHUNKID_UPDATE_MODEM,
	XXXXMICROCHUNKID_LAST_MUZZLE,
	XXXMICROCHUNKID_PENDING_TIMER,
	XXXMICROCHUNKID_TARGETING_MUZZLE,
	XXXMICROCHUNKID_TARGET_POSITION,
	XXXMICROCHUNKID_WEAPON_PARAMS_NAME,
	XXXMICROCHUNKID_MODEL_NAME,
	MICROCHUNKID_MODEL_PTR,
	XXXMICROCHUNKID_DYNAMIC_ERROR_ANGLE,
	MICROCHUNKID_CLIP_ROUNDS,
	MICROCHUNKID_INVENTORY_ROUNDS,
	MICROCHUNKID_BURST_TIMER,
	MICROCHUNKID_BURST_COUNT,
	XXX_MICROCHUNKID_AMMO_PARAMS_NAME,
	XXXMICROCHUNKID_AMMO_DEFINITION_NAME,
	XXXMICROCHUNKID_DEFINITION_NAME,
	XXXMICROCHUNKID_AMMO_DEFINITION_ID,
	MICROCHUNKID_DEFINITION_ID,
	MICROCHUNKID_IS_SECONDARY_TRIGGERED,
	MICROCHUNKID_C4_DETONATION_MODE,
	MICROCHUNKID_TARGET_LOCATION,
	MICROCHUNKID_WEAPON_EXISTS,
	MICROCHUNKID_SAFETY_SET,
	XXXMICROCHUNKID_OWNER_ERROR_ANGLE,
};

bool	WeaponClass::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_VARIABLES );

		if ( Model ) {
#if 0
			csave.Begin_Micro_Chunk( MICROCHUNKID_MODEL_NAME );
			const char * model_name = Model->Get_Name();
			csave.Write( model_name, strlen( model_name ) + 1 );
			csave.End_Micro_Chunk();
#endif
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MODEL_PTR, Model );
		}
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE_TIMER, StateTimer );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_PRIMARY_TRIGGERED, IsPrimaryTriggered );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_SECONDARY_TRIGGERED, IsSecondaryTriggered );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ROUNDS_FIRED, TotalRoundsFired );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_NEXT_ANIM_STATE, NextAnimState );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CURR_ANIM_STATE, CurrentAnimState );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_UPDATE_MODEM, UpdateModel );
		WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_CLIP_ROUNDS, ClipRounds, int );
		WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_INVENTORY_ROUNDS, InventoryRounds, int );
		WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_BURST_TIMER, BurstDelayTimer, float );
		WRITE_SAFE_MICRO_CHUNK( csave, MICROCHUNKID_BURST_COUNT, BurstCount, int );
		int def_id = Definition->Get_ID();
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEFINITION_ID, def_id );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_C4_DETONATION_MODE, C4DetonationMode );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TARGET_LOCATION, Target );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WEAPON_EXISTS, WeaponExists );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SAFETY_SET, SafetySet );

	csave.End_Chunk();

	if ( Owner != NULL ) {
		csave.Begin_Chunk( CHUNKID_OWNER_REF );
		Owner.Save( csave );
		csave.End_Chunk();
	}

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


	// Don't need to save DidFire
	// Don't need to save ContinuousEmitters;
	// Don't need to save ContinuousSound;
	// Don't need to save FiringSound;
	// Dont need to save BulletBumpTime

	return true;
}

bool	WeaponClass::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_STATE, State );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE_TIMER, StateTimer );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_PRIMARY_TRIGGERED, IsPrimaryTriggered );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_SECONDARY_TRIGGERED, IsSecondaryTriggered );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ROUNDS_FIRED, TotalRoundsFired );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_NEXT_ANIM_STATE, NextAnimState );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_CURR_ANIM_STATE, CurrentAnimState );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_UPDATE_MODEM, UpdateModel );
						READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_CLIP_ROUNDS, ClipRounds, int );
						READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_INVENTORY_ROUNDS, InventoryRounds, int );
						READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_BURST_TIMER, BurstDelayTimer, float );
						READ_SAFE_MICRO_CHUNK( cload, MICROCHUNKID_BURST_COUNT, BurstCount, int );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEFINITION_ID, def_id );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_C4_DETONATION_MODE, C4DetonationMode );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TARGET_LOCATION, Target );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_WEAPON_EXISTS, WeaponExists );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_SAFETY_SET, SafetySet );

#if 0
						case XXX_MICROCHUNKID_MODEL_NAME:
						{
							WWASSERT( Model == NULL );
							char	model_name[80];
							cload.Read( model_name, cload.Cur_Micro_Chunk_Length() );
							RenderObjClass * robj = WW3DAssetManager::Get_Instance()->Create_Render_Obj( model_name );

							Set_Model( robj );

							cload.Close_Micro_Chunk();
							cload.Open_Micro_Chunk();
							WWASSERT( cload.Cur_Micro_Chunk_ID() == MICROCHUNKID_MODEL_PTR );
							void * old_ptr = NULL;
							cload.Read( &old_ptr, sizeof( old_ptr ) );
							SaveLoadSystemClass::Register_Pointer( old_ptr, robj );
							REF_PTR_RELEASE( robj );
							break;
						}
#endif

						READ_MICRO_CHUNK( cload, MICROCHUNKID_MODEL_PTR, Model );

						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();
				}

				Init( WeaponManager::Find_Weapon_Definition( def_id ) );

				break;
			}

			case CHUNKID_OWNER_REF:
				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();
	}

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

	// Legacy
	if ( ClipRounds == 0 && InventoryRounds < 0 ) {
		ClipRounds = Definition->ClipSize;
	}

	return true;
}


void	WeaponClass::Set_Owner( ArmedGameObj *owner )
{
	Owner = owner;
}

void	WeaponClass::Set_Target_Object( PhysicalGameObj * obj )
{
	TargetObject = obj;
}

PhysicalGameObj *	WeaponClass::Get_Target_Object( PhysicalGameObj * obj )
{
	return ( PhysicalGameObj * )TargetObject.Get_Ptr();
}

void	WeaponClass::Set_Model( RenderObjClass *model )
{
	if ( Model != NULL ) {
		Model->Release_Ref();
	}
	Model = model;
	if ( Model != NULL ) {
		Model->Add_Ref();
		Init_Muzzle_Flash( Model );
	}
}

void	WeaponClass::Select( void )
{
//	Debug_Say(( "Weapon Selected\n" ));
	Set_State( STATE_START_SWITCH );
	UpdateModel = WEAPON_MODEL_UPDATE_WILL_BE_NEEDED;
}

void	WeaponClass::Deselect( void )
{
	Do_Continuous_Effects( false );
	IsPrimaryTriggered = false;
	IsSecondaryTriggered = false;
	LastFrameIsPrimaryTriggered = false;
	LastFrameIsSecondaryTriggered = false;
}

void	WeaponClass::Next_C4_Detonation_Mode( void )
{
#define	LAST_C4_DETONATION_MODE	3
	if ( ++C4DetonationMode > LAST_C4_DETONATION_MODE ) {
		C4DetonationMode = 1;
	}
	Debug_Say(( "Weapon Next C4 Detonation Mode %d\n", C4DetonationMode ));
}


/*
**
*/
void	WeaponClass::Set_Primary_Triggered( bool triggered )
{
	if ( !LockTriggers ) {
		IsPrimaryTriggered = triggered;
	}
}

void	WeaponClass::Set_Secondary_Triggered( bool triggered )
{
	if ( !LockTriggers ) {
		IsSecondaryTriggered = triggered;
	}
}

/*
** AMMO
*/
void	WeaponClass::Add_Rounds( int num )
{
	// If we have an empty clip ( first time )  load directly
	if ( ClipRounds == 0 ) {
		if ( num < 0 ) {
			ClipRounds = Definition->ClipSize;
		} else if ( InventoryRounds >= 0 )  {
			int count = MIN( num, (int)Definition->ClipSize );
			num -= count;
			ClipRounds = count;
		}
	}

	if (	CheatMgrClass::Get_Instance () != NULL &&
			CheatMgrClass::Get_Instance ()->Is_Cheat_Enabled( CheatMgrClass::CHEAT_INFINITE_AMMO ) && Owner == COMBAT_STAR )
	{
		InventoryRounds = Definition->MaxInventoryRounds ;
	} else if ( num < 0 ) {
		InventoryRounds = -1;
	} else if ( InventoryRounds >= 0 )  {
		InventoryRounds += num;
	}

	if ( (InventoryRounds != -1) && (InventoryRounds > (int)Definition->MaxInventoryRounds) ) {
		InventoryRounds = Definition->MaxInventoryRounds ;
	}

}

int	WeaponClass::Get_Total_Rounds( void )
{
	if ( InventoryRounds < 0 ) {
		return -1;
	}

	//
	//	Check to see if the infinite ammo cheat is enabled
	//
	if (	CheatMgrClass::Get_Instance () != NULL &&
			CheatMgrClass::Get_Instance ()->Is_Cheat_Enabled( CheatMgrClass::CHEAT_INFINITE_AMMO ) && Owner == COMBAT_STAR )
	{
		return -1;
	}

	return ClipRounds + InventoryRounds;
}

void	WeaponClass::Set_Total_Rounds( int num )
{
	if (	CheatMgrClass::Get_Instance () != NULL &&
			CheatMgrClass::Get_Instance ()->Is_Cheat_Enabled( CheatMgrClass::CHEAT_INFINITE_AMMO ) && Owner == COMBAT_STAR )
	{
		InventoryRounds = Definition->MaxInventoryRounds;
	} else if ( num == -1 ) {
		InventoryRounds = -1;
	} else {
		// Assume what you have in inventory is correct
		num -= (int)InventoryRounds;
		// And the rest goes in the clip
		ClipRounds = num;

		// Fix a negative clip
		if ( ClipRounds < 0 ) {
			InventoryRounds += ClipRounds;
			ClipRounds = 0;
		}

		// Fix overloaded clip
		if ( ClipRounds > Definition->ClipSize ) {
			InventoryRounds += ClipRounds - Definition->ClipSize;
			ClipRounds -= ClipRounds - Definition->ClipSize;
		}

		// Fix Overloaded inventory
		if ( InventoryRounds > (int)Definition->MaxInventoryRounds ) {
			InventoryRounds = Definition->MaxInventoryRounds;
		}
	}
}

bool	WeaponClass::Is_Ammo_Maxed( void )
{
	if (InventoryRounds == -1) return true;

	// Special case for C4
	if (((int)Definition->MaxInventoryRounds == 0) && (ClipRounds == 0)) return false;

	return (InventoryRounds == (int)Definition->MaxInventoryRounds);
}

void	WeaponClass::Decrement_Rounds( int rounds )
{
	if ( ClipRounds != -1 ) {
		ClipRounds -= rounds;
		if ( ClipRounds <= 0 ) {
			ClipRounds = 0;
		}
	}
}

void	WeaponClass::Do_Reload( void )
{
	int added = (int)Definition->ClipSize - (int)ClipRounds;

	//
	//	Check to see if the infinite ammo cheat is enabled
	//
	bool apply_cheat = false;
	if (	CheatMgrClass::Get_Instance () != NULL &&
			CheatMgrClass::Get_Instance ()->Is_Cheat_Enabled( CheatMgrClass::CHEAT_INFINITE_AMMO ) && Owner == COMBAT_STAR )
	{
		apply_cheat = true;
	}

	if ( apply_cheat == false && InventoryRounds >= 0 ) {

		if ( InventoryRounds < added ) {
			added = InventoryRounds;
		}

		InventoryRounds -= added;
	}

	ClipRounds += added;
}

float	WeaponClass::Get_Range( void )
{
	return ( PrimaryAmmoDefinition != NULL ) ? PrimaryAmmoDefinition->Range : 0;
}


/*
**	Fire_C4
*/
void	WeaponClass::Fire_C4( const AmmoDefinitionClass *ammo_def )
{
	#define		C4_OBJECT_NAME				"Tossed C4"

	// C4 special case
	C4GameObj *c4 = (C4GameObj *)ObjectLibraryManager::Create_Object( C4_OBJECT_NAME );
	if ( c4 ) {
		c4->Init_C4( ammo_def, Get_Owner()->As_SoldierGameObj(), C4DetonationMode, Get_Muzzle() );
	}

	return ;
}


/*
**	Fire_Beacon
*/
bool	WeaponClass::Fire_Beacon( const AmmoDefinitionClass *ammo_def )
{
	bool retval = false;

	// only for server
	if ( CombatManager::I_Am_Server() ) {
		//
		//	Create the beacon
		//
		BeaconGameObj *beacon = (BeaconGameObj *)ObjectLibraryManager::Create_Object( ammo_def->BeaconDefID );
		if ( beacon != NULL ) {

			//
			//	Get the position of the owner
			//
			Vector3 pos;
			Get_Owner()->Get_Position( &pos );
			beacon->Init_Beacon( Definition, Get_Owner()->As_SoldierGameObj(), pos );
			beacon->Start_Observers ();

			//
			//	Begin arming the beacon
			//
			if ( beacon->Can_Place_Here ( pos ) ) {
				beacon->Begin_Arming();
				retval = true;
			} else {
				beacon->Set_Delete_Pending ();
			}
		}
	}

	return retval;
}


/*
**
*/
void	WeaponClass::Do_Fire( bool primary )
{
	WWPROFILE( "Do Fire" );

	// Stats
	if (	Get_Owner() && 
			Get_Owner()->As_SoldierGameObj() && 
			Get_Owner()->As_SoldierGameObj()->Get_Player_Data() ) {
		Get_Owner()->As_SoldierGameObj()->Get_Player_Data()->Stats_Add_Shot_Fired();

		Get_Owner()->As_SoldierGameObj()->Get_Player_Data()->Stats_Add_Weapon_Fired( 
			this->Get_Definition()->Get_ID() );

	}

	//	Debug_Say(( "Fire at %f\n", (float)WW3D::Get_Sync_Time() ));

	const AmmoDefinitionClass *ammo_def;
	if ( primary ) {
		ammo_def = PrimaryAmmoDefinition;
	} else {
		ammo_def = SecondaryAmmoDefinition;
	}

//	Debug_Say(( "Firing from %f %f %f\n", muzzle_pos.X, muzzle_pos.Y, muzzle_pos.Z ));
//	Debug_Say(( "Firing at %f %f %f\n", target_pos.X, target_pos.Y, target_pos.Z ));
//	Debug_Say(( " %d Clips  %d in Clip\n", NumClips, ClipContents ));

	//
	//	Handle each weapon type differently
	//
	if ( Get_Style() == WEAPON_HOLD_STYLE_BEACON ) {

		//
		//	Try to fire the beacon
		//
		if ( Fire_Beacon( ammo_def ) ) {

			//
			//	Decrement the rounds
			//
//			WWASSERT( BurstCount != 0 );
			BurstCount--;
//			WWASSERT( ClipRounds != 0 );
			Decrement_Rounds( ammo_def->SprayBulletCost );
		}

	} else {

		//
		//	Decrement the rounds
		//
//		WWASSERT( BurstCount != 0 );
		BurstCount--;
//		WWASSERT( ClipRounds != 0 );
		Decrement_Rounds( ammo_def->SprayBulletCost );

		if ( Get_Style() == WEAPON_HOLD_STYLE_C4 ) {
			if ( CombatManager::I_Am_Server() ) {	// Only on servers
				Fire_C4( ammo_def );
			}
		} else {
			Fire_Bullet( ammo_def, primary );
		}
	}

	return ;
}

/*
**	Fire_Bullet
*/
void	WeaponClass::Fire_Bullet( const AmmoDefinitionClass *ammo_def, bool primary )
{
	WWPROFILE( "Fire Bullet" );

	if ( Get_Owner() == COMBAT_STAR ) {
		Vector3 pos;
		COMBAT_STAR->Get_Position( &pos );
	   DIAG_LOG(( "WFRD", "%1.2f; %1.2f; %1.2f; %s; %d; %d", pos.X, pos.Y, pos.Z, Get_Definition()->Get_Name(), !primary, Get_Total_Rounds() ));
	}

	int spray_count = ammo_def->SprayCount;
	if ( spray_count < 1 ) {
		spray_count = 1;
	}

	int muzzle_index = (Get_Total_Rounds_Fired() & 1) + ( primary ? 0 : 2 );

	if ( !Get_Owner()->Muzzle_Exists( muzzle_index ) ) {
		// if the second muzzle doesn't exist, use the first
		if ( muzzle_index & 1 ) {
			muzzle_index--;
		}
	}

	Matrix3D muzzle = Get_Muzzle( muzzle_index );

	// Special test for first person
	if ( CombatManager::Is_First_Person() && Get_Owner() == COMBAT_STAR ) {
		Vector3	fp_muzzle_pos = WeaponViewClass::Get_Muzzle_Pos();

		// cast to see what we hit
		Vector3 start = fp_muzzle_pos;
		Vector3 end = Get_Owner()->Get_Targeting_Pos();
		LineSegClass ray( start, end );
		CastResultStruct res;
		PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

		Ignore_Owner();

{		WWPROFILE( "Cast_Ray" );
		COMBAT_SCENE->Cast_Ray( raytest );
}
		Unignore_Owner();

		// If the hit point is near the targeting_pos, use the first person muzzle
		if ( raytest.Result->Fraction >= 0.99F ) {
			muzzle.Obj_Look_At( fp_muzzle_pos, Get_Owner()->Get_Targeting_Pos(), 0 );
		}
	}

	Vector3 bullet_target(0,0,0);
	DamageableGameObj * target_obj = NULL;

	if ( ammo_def->IsTracking ) {
		// determine the target
		Vector3 start = muzzle.Get_Translation();

		// (gth) this modification makes bikes able to target anything even though their muzzle doesn't rotate...
		Vector3 end = Get_Owner()->Get_Targeting_Pos(); //muzzle * Vector3( ammo_def->Range, 0, 0 );

		LineSegClass ray( start, end );
		CastResultStruct res;
		PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

		Ignore_Owner();

{ WWPROFILE( "Cast Ray" );
		COMBAT_SCENE->Cast_Ray( raytest );
}

		Unignore_Owner();

		ray.Compute_Point( raytest.Result->Fraction, &end );
		bullet_target = end;

		if ( raytest.CollidedPhysObj != NULL && raytest.CollidedPhysObj->Get_Observer() != NULL ) {
			target_obj = ((CombatPhysObserverClass *)raytest.CollidedPhysObj->Get_Observer())->As_DamageableGameObj();
			// No homing on buildings
			if ( target_obj->As_BuildingGameObj() != NULL ) {
				target_obj = NULL;
			}
		}
	}

	for ( int count = 0; count < spray_count; count++ ) {

		Vector3 velocity = muzzle.Get_X_Vector() * ammo_def->Velocity;


#if 0
		if ( ammo_def->SprayAngle ) {
			// Use Spray Angle
			Vector3	spray_adjust = RANDOM_VECTOR( 1.0 );
			spray_adjust.Normalize();
			spray_adjust = spray_adjust * ammo_def->Velocity;
			spray_adjust *= ammo_def->SprayAngle / (float)(2*WWMATH_PI);

			velocity += spray_adjust;
			velocity.Normalize();
			velocity = velocity * ammo_def->Velocity;
		}

		if ( DynamicErrorAngle ) {
			velocity += RANDOM_VECTOR( DynamicErrorAngle / 2 );
			velocity.Normalize();
			velocity = velocity * ammo_def->Velocity;
		}
#else
		Matrix3D	VelTM;
		// Look at backwards with random roll
		VelTM.Look_At( velocity, Vector3( 0,0,0 ), FreeRandom.Get_Float( DEG_TO_RADF( 360.0f ) ) );
#define	SPRAY_CHEAT	0
#if SPRAY_CHEAT
		float error_angle = 0;
#else
		float error_angle = ammo_def->SprayAngle;
#endif
		float rand_angle = FreeRandom.Get_Float() * error_angle;
		VelTM.Rotate_Y( rand_angle );
		velocity = VelTM.Get_Z_Vector();
		velocity.Normalize();
		velocity *= ammo_def->Velocity;
#endif

		//Vector3 position = muzzle.Get_Translation();
		Vector3 position;
		Compute_Bullet_Start_Point(muzzle,&position);

		BulletBumpTime += ammo_def->AliasedSpeed * TimeManager::Get_Frame_Seconds() * 1.3f;
		BulletBumpTime = WWMath::Wrap( BulletBumpTime, 0, TimeManager::Get_Frame_Seconds() );

		float bump_time = BulletBumpTime;

		if ( (float)ammo_def->Velocity != 0 ) {
			bump_time += 1 / (float)ammo_def->Velocity;  // Skip the first meter
		}

		//Debug_Say(( "%f %f\n", BulletBumpTime,bump_time ));

		if ( Target.Length2() != 0 || !ammo_def->IsTracking ) {
			bullet_target = Target;
		}

		// Bullets from vehicles belong to the gunner
		ArmedGameObj * bullet_owner = Get_Owner();
		if ( bullet_owner != NULL && bullet_owner->As_SmartGameObj() ) {
			VehicleGameObj * vehicle = bullet_owner->As_SmartGameObj()->As_VehicleGameObj();
			if ( ( vehicle ) && ( vehicle->Get_Actual_Gunner() != NULL ) ) {
				bullet_owner = vehicle->Get_Actual_Gunner();
			}
		}

		// Create a bullet
		BulletManager::Create_Bullet( ammo_def, position, velocity, bullet_owner, bump_time, bullet_target, target_obj );
	}

	if ( ammo_def->DisplayLaser ) {
		TimedDecorationPhysClass * laser_obj = NEW_REF( TimedDecorationPhysClass, () );
		if ( laser_obj ) {

			// Find where the laser should stop
			Vector3 start = muzzle.Get_Translation();
			Vector3 end = muzzle * Vector3( ammo_def->Range, 0, 0 );
			LineSegClass ray( start, end );
			CastResultStruct res;
			PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

			Ignore_Owner();

{ WWPROFILE( "Cast Ray" );
			COMBAT_SCENE->Cast_Ray( raytest );
}

			Unignore_Owner();

			ray.Compute_Point( raytest.Result->Fraction, &end );

			// Create a laser
			RenderObjClass * model = NEW_REF( Line3DClass, ( start, end, 0.1f,  0.8f,0,0,  0.8f ) );
			laser_obj->Set_Model( model );
			model->Release_Ref();
			laser_obj->Set_Lifetime( 0.6f );
			laser_obj->Update_Cull_Box();	// htis may be needed due to a bug in Line3DClass
			COMBAT_SCENE->Add_Dynamic_Object( laser_obj );
			laser_obj->Release_Ref();
		}
	}

	// Initiate recoil effects on our parent object.
	Get_Owner()->Start_Recoil(muzzle_index,Get_Recoil_Scale(),Get_Recoil_Time());

	TotalRoundsFired++;
}


bool	WeaponClass::Is_Muzzle_Clear()
{
	int primary = 1;
	int muzzle_index = Get_Total_Rounds_Fired() & 1 + ( primary ? 0 : 2 );
	Matrix3D muzzle = Get_Muzzle( muzzle_index );

	Vector3 start_pt;
	Vector3 end_pt;
	muzzle.Get_Translation( &end_pt );

	if ( Get_Owner() == NULL ) {
		return true;
	}

	if ((Get_Owner() == COMBAT_STAR) && CombatManager::Is_First_Person()) {
		return true;
	}

	if (Get_Owner()->As_SoldierGameObj() != NULL) {
	
		start_pt = Get_Owner()->Get_Bullseye_Position();

	} else {

		// Estimate the distance we need to sweep back along the muzzle in order to be inside the vehicle.
		Vector3 owner_pos;
		Get_Owner()->Get_Position( &owner_pos );
		owner_pos -= end_pt;
		owner_pos.Z = 0.0f;
		float dist = owner_pos.Quick_Length();

		// Compute the starting point for a ray to test if the muzzle is inside anything.
		Vector3 offset;
		Matrix3D::Rotate_Vector(muzzle,Vector3(-dist,0.0f,0.0f),&offset);
		start_pt = end_pt + offset;

	}

	// Cast the ray
	LineSegClass ray( start_pt, end_pt );
	CastResultStruct res;
	PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);
	raytest.CheckDynamicObjs = false;

	{ 
		WWPROFILE( "Cast Ray" );
		COMBAT_SCENE->Cast_Ray( raytest );
	}

	return raytest.Result->Fraction == 1.0f;
}

void WeaponClass::Compute_Bullet_Start_Point(const Matrix3D & muzzle,Vector3 * set_start_point)
{
	//
	// The default behavior is to start the bullet at the position of the muzzle.
	//
	muzzle.Get_Translation(set_start_point);

	// 
	// If this weapon is owned by a vehicle, try to ensure the bullet starts in front
	// of any dynamic objects that may be between the muzzle point and the body of the vehicle
	//
	if (Get_Owner()->As_VehicleGameObj() != NULL) {
		Vector3 ray_start;
		Vector3 ray_end;
		muzzle.Get_Translation( &ray_end );

		// Estimate the distance we need to sweep back along the muzzle in order to be inside the vehicle.
		Vector3 owner_pos;
		Get_Owner()->Get_Position( &owner_pos );
		owner_pos -= ray_end;
		owner_pos.Z = 0.0f;
		float dist = owner_pos.Quick_Length();

		// Compute the starting point for a ray to test if the muzzle is inside anything.
		Vector3 offset;
		Matrix3D::Rotate_Vector(muzzle,Vector3(-dist,0.0f,0.0f),&offset);
		ray_start = ray_end + offset;

		// Cast the ray
		LineSegClass ray( ray_start, ray_end );
		CastResultStruct res;
		PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);
		raytest.CheckStaticObjs = false;

		{ 
			Ignore_Owner();

			WWPROFILE( "Cast Ray" );
			COMBAT_SCENE->Cast_Ray( raytest );

			Unignore_Owner();
		}

		if (res.Fraction < 1.0f) {
			ray.Compute_Point(res.Fraction,set_start_point);
		}
	}
}

void	WeaponClass::Do_Firing_Effects( void )
{
	if ( TimeManager::Get_Frame_Seconds() == 0 ) {
		return;	// No sounds when time stops
	}

	WWPROFILE( "Firing Effects" );

	Matrix3D	muzzle = Get_Muzzle();

	// Don't do these two if fired by the star in First Person Mode
	if ( (Get_Owner() != COMBAT_STAR) || !CombatManager::Is_First_Person() ) {

#if 01
		// create muzzle flash
		if ( Definition->MuzzleFlashPhysDefID != 0 ) {

			DefinitionClass * def = DefinitionMgrClass::Find_Definition( Definition->MuzzleFlashPhysDefID );
			if ( def != NULL ) {
				WWASSERT( ((PhysDefClass *)def)->Is_Type( "TimedDecorationPhysDef" ) );
				TimedDecorationPhysClass * muzzle_flash = (TimedDecorationPhysClass *)def->Create();
				if ( muzzle_flash ) {
					RenderObjClass * model = muzzle_flash->Peek_Model();
					if ( model != NULL ) {
//						muzzle.Rotate_X( FreeRandom.Get_Float( DEG_TO_RAD( 360 ) ) );
						muzzle_flash->Set_Transform( muzzle );
						COMBAT_SCENE->Add_Dynamic_Object( muzzle_flash );

					// If this is an emitter, start it
						if ( model->Class_ID() == RenderObjClass::CLASSID_PARTICLEEMITTER ) {
							ParticleEmitterClass * pe = (ParticleEmitterClass *)model;
							pe->Start();
						}
					} else {
						Debug_Say(( "Missing muzzle flash for %s\n", Definition->Get_Name() ));
					}

					muzzle_flash->Release_Ref();
				}
			} else {
				//Debug_Say(( "Couldn't find muzzle flash def for %s\n", Definition->Get_Name() ));
			}
		}
#endif

		// if this gun is fired by the STAR and they have an eject bone, eject a shell.
		if ((Get_Owner() == COMBAT_STAR) && (Model != NULL)) {
			WWPROFILE( "Eject" );
			int eject_index = Model->Get_Bone_Index( "eject" );
			if ( eject_index > 0 ) {
				Make_Shell_Eject( Model->Get_Bone_Transform( eject_index ) );
			}
		}
	}

	// Make the Gunshot sound

	//
	//	Determine which sound to play, the primary or secondary firing sound?
	//
	int sound_id = 0;
	if ( IsPrimaryTriggered && PrimaryAmmoDefinition != NULL ) {
		sound_id = PrimaryAmmoDefinition->FireSoundDefID;
	} else if ( IsSecondaryTriggered && SecondaryAmmoDefinition != NULL ) {
		sound_id = SecondaryAmmoDefinition->FireSoundDefID;
	}

	//
	//	Release the old firing sound (if necessary)
	//
	bool release_curr_sound = false;
	if ( sound_id != FiringSoundDefID && FiringSound != NULL ) {
		release_curr_sound = true;
	}

	//
	//	For first-person we make the weapon sounds "2D", so check to see
	// if we need to re-create the sound...
	//
	if ( FiringSound != NULL ) {
		if ( Get_Owner() == COMBAT_STAR && CombatManager::Is_First_Person() ) {
			
			//
			//	In first person, we need the sound to be "2D"
			//
			if ( FiringSound->Get_Class_ID () != CLASSID_2D ) {
				release_curr_sound = true;
			}
		} else {

			//
			//	In third person, we need the sound to be "3D"
			//
			if ( FiringSound->Get_Class_ID () == CLASSID_2D ) {
				release_curr_sound = true;
			}
		}
	}

	//
	//	Release the currently playing sound as necessary
	//	
	if ( release_curr_sound ) {		
		FiringSound->Remove_From_Scene ();
		REF_PTR_RELEASE (FiringSound);
		FiringSoundDefID = 0;
	}

	//
	//	Do we need to recreate the firing sound object, or can we just
	// reuse the one we already have?
	//
	if ( FiringSound != NULL ) {

		//
		// Stop the current sound
		//
   	//FiringSound->Set_Transform( muzzle );
		//FiringSound->Stop();		

		if ( FiringSound->Get_Class_ID () == CLASSID_3D ) {
			FiringSound->Stop();
		} else {
			FiringSound->Seek( 0 );
		} 
		
		
		//
		//	Force the sound to play
		//
		if ( Get_Owner() != COMBAT_STAR || CombatManager::Is_First_Person() == false ) {
			FiringSound->Add_To_Scene( true );
			FiringSound->Play();
		} else {			
			FiringSound->Play();
		}		

		//
		//	Play the sound (or add it to the scene)
		//
		/*if ( Get_Owner() == COMBAT_STAR && CombatManager::Is_First_Person() ) {
			FiringSound->Play();
		} else {
			FiringSound->Remove_From_Scene();
			FiringSound->Add_To_Scene( true );
		}*/

	} else {

		//
		//	Create a pseudo-3d sound effect for this firing sound and
		// add it to the sound scene.
		//

		if ( sound_id != 0 ) {
			
			//
			//	Determine whether to play the sound as 2D or 3D
			//
			int classid_hint = CLASSID_3D;
			if ( Get_Owner() == COMBAT_STAR && CombatManager::Is_First_Person() ) {
				classid_hint = CLASSID_2D;
			}

			//
			//	Create the sound
			//
			RefCountedGameObjReference *owner_ref = new RefCountedGameObjReference;
			owner_ref->Set_Ptr( Get_Owner() );
			FiringSound = WWAudioClass::Get_Instance()->Create_Sound( sound_id, owner_ref, 0, classid_hint );
			REF_PTR_RELEASE( owner_ref );
		}

		if ( FiringSound != NULL ) {

			FiringSoundDefID = sound_id;
			FiringSound->Set_Transform( muzzle );
			FiringSound->Attach_To_Object( Model );
			FiringSound->Add_To_Scene( true );
		}
	}

	// (gth) Apply an Impulse to tanks when they fires a bullet
	// Byon, this can be done with any RigidBody derived physics object (all vehicles)
	// I suggest making it a parameter of the weapon and having the strength be a
	// multiple of the mass of the vehicle.
	ArmedGameObj * owner = Get_Owner();
	VehicleGameObj * vehicle_game_obj = owner->As_VehicleGameObj();
	if (vehicle_game_obj != NULL) {
		PhysClass * vehicle = vehicle_game_obj->Peek_Physical_Object();
		if (vehicle->As_TrackedVehicleClass() != NULL) {

			RigidBodyClass * rbody = (RigidBodyClass *)vehicle;

			Vector3 impulse_pos;
			muzzle.Get_Translation(&impulse_pos);
			Vector3 impulse;
			muzzle.Get_X_Vector(&impulse);
			impulse *= -Definition->RecoilImpulse * rbody->Get_Mass();

			rbody->Apply_Impulse(impulse,impulse_pos);
		}
	}

	// Play an anim on the human owner
	if ( owner != NULL && owner->As_SoldierGameObj() ) {
		if ( !Definition->HumanFiringAnimation.Is_Empty() ) {
			owner->As_SoldierGameObj()->Set_Animation( Definition->HumanFiringAnimation, 0, 0 );
		}
	}
}

void	WeaponClass::Make_Shell_Eject( const Matrix3D & tm )
{
	DefinitionClass * def = DefinitionMgrClass::Find_Definition( Definition->EjectPhysDefID );

	if ((def != NULL) && ((PhysDefClass *)def)->Is_Type( "ProjectileDef" )) {

		// Create the object
		ProjectileClass * PhysicalObject = (ProjectileClass *)def->Create();

		// Set its transform, collision group, observer, and initial velocity
		PhysicalObject->Set_Transform( tm );
		PhysicalObject->Set_Observer(&_TheEjectCasingObserver);
		PhysicalObject->Set_Collision_Group( DEFAULT_COLLISION_GROUP );
		PhysicalObject->Set_Velocity( tm.Rotate_Vector( Vector3( 2,0,0 ) ) );

		// Add it to the scene
		COMBAT_SCENE->Add_Dynamic_Object( PhysicalObject );
		PhysicalObject->Release_Ref();
	}
}


/*
**
*/
void	WeaponClass::Do_Continuous_Effects( bool enable )
{
	if ( !enable ) {

		for ( int i = 0; i < ContinuousEmitters.Length(); i++ ) {
			if ( ContinuousEmitters[i] != NULL ) {
				// Check if it is in the scene, it may ahve already been remove by completion
				if ( ContinuousEmitters[i]->Peek_Scene() != NULL ) {
					PhysicsSceneClass::Get_Instance()->Remove_Render_Object( ContinuousEmitters[i] );
				}
				ContinuousEmitters[i]->Stop();
				ContinuousEmitters[i]->Release_Ref();
				ContinuousEmitters[i] = NULL;
			}
		}

		if ( ContinuousSound != NULL ) {
			ContinuousSound->Stop();
			ContinuousSound->Remove_From_Scene();
			ContinuousSound->Release_Ref();
			ContinuousSound = NULL;
		}
	}

	if ( enable ) {

		const AmmoDefinitionClass *ammo_def = PrimaryAmmoDefinition;
		if (IsPrimaryTriggered == false && IsSecondaryTriggered) {
			ammo_def = SecondaryAmmoDefinition;
		}

		int i;

		if ( ContinuousEmitters.Length() == 0 ) {

			int num_muzzles = 1;
			if ( Get_Muzzle( 0 ) != Get_Muzzle( 1 ) ) {
				num_muzzles = 2;
			}

			ContinuousEmitters.Resize( num_muzzles );
			for ( int i = 0; i < ContinuousEmitters.Length(); i++ ) {
				ContinuousEmitters[i] = NULL;
			}
		}

		for ( i = 0; i < ContinuousEmitters.Length(); i++ ) {
			if ( ContinuousEmitters[i] == NULL && !ammo_def->ContinuousEmitterName.Is_Empty() ) {
				RenderObjClass * renderobj = Create_Render_Obj_From_Filename( ammo_def->ContinuousEmitterName );
				if ( renderobj ) {
					WWASSERT( renderobj->Class_ID() == RenderObjClass::CLASSID_PARTICLEEMITTER );
					ContinuousEmitters[i] = (ParticleEmitterClass *)renderobj;
					ContinuousEmitters[i]->Set_Velocity_Inheritance_Factor( 1 );
				}
				if ( ContinuousEmitters[i] ) {
					SET_REF_OWNER( ContinuousEmitters[i] );
					ContinuousEmitters[i]->Start();
					PhysicsSceneClass::Get_Instance()->Add_Render_Object( ContinuousEmitters[i] );
				}
			}
		}

		if ( ContinuousSound == NULL && ammo_def->ContinuousSoundDefID != 0 ) {
			ContinuousSound = WWAudioClass::Get_Instance()->Create_Continuous_Sound( ammo_def->ContinuousSoundDefID );
			if ( ContinuousSound != NULL ) {
				ContinuousSound->Add_To_Scene( true );
			}
		}

		// if first person star, use first muzzle
		if ( (Get_Owner() == COMBAT_STAR) && CombatManager::Is_First_Person() ) {

			Vector3	fp_muzzle_pos = WeaponViewClass::Get_Muzzle_Pos();
			Matrix3D muzzle;
			muzzle.Obj_Look_At( fp_muzzle_pos, Get_Owner()->Get_Targeting_Pos(), 0 );

			for ( i = 0; i < ContinuousEmitters.Length(); i++ ) {
				if ( ContinuousEmitters[i] != NULL ) {
					ContinuousEmitters[i]->Set_Transform( muzzle );
				}
			}
			if ( ContinuousSound != NULL ) {
				ContinuousSound->Set_Transform( muzzle );
			}
		} else {
			for ( i = 0; i < ContinuousEmitters.Length(); i++ ) {
				if ( ContinuousEmitters[i] != NULL ) {
					ContinuousEmitters[i]->Set_Transform( Get_Muzzle( i ) );
				}
			}
			if ( ContinuousSound != NULL ) {
				ContinuousSound->Set_Transform( Get_Muzzle() );
			}
		}

	}

}

#define	WEAPON_RATE_CHEAT	0

/*
** Weapon State
*/
void	WeaponClass::Set_State( WeaponStateType new_state )
{
	#define	SWITCH_TIME		1.0f
	#define	RELOAD_TIME		Definition->ReloadTime
	#define	READY_TIME		25.0f

//	Debug_Say(( "Weapon switching from state %d to %d\n", State, new_state ));

	State = new_state;

	if ( State == STATE_START_SWITCH )	StateTimer = SWITCH_TIME/2.0f;
	if ( State == STATE_END_SWITCH )		StateTimer = SWITCH_TIME/2.0f;
	if ( State == STATE_RELOAD )			StateTimer = (float) RELOAD_TIME;
	if ( State == STATE_READY )			StateTimer = READY_TIME;	// delay til IDLE
	if ( State == STATE_FIRE_PRIMARY )	StateTimer = 1.0f / PrimaryAmmoDefinition->RateOfFire;
	if ( State == STATE_FIRE_SECONDARY )StateTimer = 1.0f / SecondaryAmmoDefinition->RateOfFire;
	if ( State == STATE_CHARGE )			StateTimer = PrimaryAmmoDefinition->ChargeTime;	// Charge Time

#if WEAPON_RATE_CHEAT
	if ( Get_Owner() == COMBAT_STAR ) {
		StateTimer *= 0.2f;
	}
#endif

}

void	WeaponClass::Update_State( float pending_time )
{
	LockTriggers = false;		// Unlock triggers

	WWPROFILE( "Update weapon state" );
	while ( pending_time > 0.0f ) {

		bool trigger_ok = true;
		// Only fire beacon if state is upright
		if ( Get_Style() == WEAPON_HOLD_STYLE_BEACON && 
				Get_Owner() && Get_Owner()->As_SoldierGameObj() && 
				!Get_Owner()->As_SoldierGameObj()->Is_Upright() ) {
			trigger_ok = false;
		}

		// If we can fire, start charging, it will fire and go back to READY
		if ( (State == STATE_READY) || (State == STATE_IDLE) ) {
			if ( (IsPrimaryTriggered || IsSecondaryTriggered) && (BurstCount != 0) && CombatManager::Is_Gameplay_Permitted() && trigger_ok ) {
				if ( SafetySet ) {
					LockTriggers = true;
				} else {
					if ( Is_Loaded() ) {
						Set_State( STATE_CHARGE );
					} else {
						// We are not loaded, play the empty sound
#if 0
						AudibleSoundClass * sound = WWAudioClass::Get_Instance()->Create_Sound( "Pistol_Empty_Click" );
						if ( sound ) {
							sound->Play();
							sound->Release_Ref();
						}
#else
						if ( Definition->EmptySoundDefID != 0 ) {
							EmptySoundTimer -= TimeManager::Get_Frame_Seconds();
							if ( EmptySoundTimer <= 0 ) {
								EmptySoundTimer = 0.3f;
								Matrix3D	muzzle = Get_Muzzle();

								RefCountedGameObjReference *owner_ref = new RefCountedGameObjReference;
								owner_ref->Set_Ptr( Get_Owner() );

								AudibleSoundClass * sound = WWAudioClass::Get_Instance()->Create_Sound( Definition->EmptySoundDefID, owner_ref );
								if ( sound != NULL ) {
									sound->Set_Transform( muzzle );
									sound->Attach_To_Object( Model );
									sound->Add_To_Scene( true );
									sound->Release_Ref();
								}

								REF_PTR_RELEASE( owner_ref );
							}
						}
#endif
					}
				}
			}

			// Force a reload when empty
			if ( Is_Reload_Needed() ) {
				Force_Reload();
			}
		}

		float	useable_time = min( StateTimer, pending_time );
		StateTimer -= useable_time;
		pending_time -= useable_time;

		if ( StateTimer <= 0 ) {	// Time for new state

			switch ( State ) {

				case STATE_IDLE:
					StateTimer = 0;
					pending_time = 0;
					break;

				case STATE_READY:
					Set_State( STATE_IDLE );
					break;

				case STATE_CHARGE:
					if ( IsPrimaryTriggered || !IsSecondaryTriggered ) {
						Set_State( STATE_FIRE_PRIMARY );
					} else {
						Set_State( STATE_FIRE_SECONDARY );
					}

					if ( IsPrimaryTriggered  || IsSecondaryTriggered ) {
						if ( Is_Muzzle_Clear() ) {
							Do_Fire( IsPrimaryTriggered );
							DidFire = true;
						}
					}
					break;

				case STATE_FIRE_PRIMARY:
				case STATE_FIRE_SECONDARY:
					Set_State( STATE_READY );
					break;

				case STATE_RELOAD:
					Do_Reload();
					Set_State( STATE_READY );
					break;

				case STATE_START_SWITCH:
					if ( UpdateModel == WEAPON_MODEL_UPDATE_WILL_BE_NEEDED ) {
						UpdateModel = WEAPON_MODEL_UPDATE_IS_NEEDED;
					}
					Set_State( STATE_END_SWITCH );
					break;

				case STATE_END_SWITCH:
					Set_State( STATE_IDLE );
					break;
			}
		}
	}
}

void	WeaponClass::Force_Reload( void )
{
	// Stop reloading on last bullet to fire faster
//	if ( Is_Reload_OK() && State != STATE_RELOAD ) {
	if ( Is_Reload_OK() && State <= STATE_READY ) {
		Set_State( STATE_RELOAD );

		if ( Definition->ReloadSoundDefID != 0 ) {
			// Make the reload sound
			Matrix3D	muzzle = Get_Muzzle();

			RefCountedGameObjReference *owner_ref = new RefCountedGameObjReference;
			owner_ref->Set_Ptr( Get_Owner() );

			AudibleSoundClass * sound = WWAudioClass::Get_Instance()->Create_Sound( Definition->ReloadSoundDefID, owner_ref );
			if ( sound != NULL ) {
				sound->Set_Transform( muzzle );
				sound->Attach_To_Object( Model );
				sound->Add_To_Scene( true );
				sound->Release_Ref();
			}

			REF_PTR_RELEASE( owner_ref );
		}
	}
}

/*
**
*/
void	WeaponClass::Update( void )
{
	// Remove player weapons from vehicle drivers
	if (	Get_Owner() && 
			Get_Owner()->As_SoldierGameObj() && 
			Get_Owner()->As_SoldierGameObj()->Is_In_Vehicle() ) {
		IsPrimaryTriggered = false;
		IsSecondaryTriggered = false;
	}

	if (	Get_Style() == WEAPON_HOLD_STYLE_C4 ||
			Get_Style() == WEAPON_HOLD_STYLE_BEACON ||
			Get_Can_Snipe() )
	{
		IsSecondaryTriggered = false;
	}

	WWASSERT( PrimaryAmmoDefinition != NULL );

	// Update Burst logic
	if ( (int)PrimaryAmmoDefinition->BurstMax == 0 ) {	// if not using bursts
		BurstCount = -1;
	} else {
		BurstDelayTimer -= TimeManager::Get_Frame_Seconds();
		if ( (!IsPrimaryTriggered && !IsSecondaryTriggered) || ( BurstDelayTimer <= 0.0f ) ) {
			BurstDelayTimer = PrimaryAmmoDefinition->BurstDelayTime * FreeRandom.Get_Float( 0.50f, 1.0f );
			BurstCount = PrimaryAmmoDefinition->BurstMax;
		}
	}

	if ( TimeManager::Get_Frame_Seconds() != 0 ) {
		DidFire = false;	// reset the did fire flag for this frame
	}

	Update_State( TimeManager::Get_Frame_Seconds() );

	// If star in first person, force no muzzle flashes on 3rd person model
	if ( (Get_Owner() == COMBAT_STAR) && CombatManager::Is_First_Person() ) {
		Update_Muzzle_Flash( false, false );
	} else {
		Update_Muzzle_Flash( DidFire && (TotalRoundsFired & 1), DidFire && (~TotalRoundsFired & 1) );
	}

	if ( DidFire ) {
		Do_Firing_Effects();	// Create muzzle flash and sounds
		NextAnimState = (TotalRoundsFired & 1) ? WEAPON_ANIM_FIRING_0 : WEAPON_ANIM_FIRING_1;
	} else {
		NextAnimState = WEAPON_ANIM_NOT_FIRING;
	}

	//
	//	Remove any continuous effects if the trigger state has changed since last frame
	//
	if (	(IsPrimaryTriggered == false && LastFrameIsPrimaryTriggered == true) ||
			(IsSecondaryTriggered == false && LastFrameIsSecondaryTriggered == true) )
	{
		Do_Continuous_Effects( false );
	} else {

		//
		//	Determine whether or not to trigger the continuous effects (emitters) for
		// this weapon.
		//
		bool emitters_on = IsPrimaryTriggered || IsSecondaryTriggered;
		if ( State == STATE_START_SWITCH ||  State == STATE_END_SWITCH || 
			  State == STATE_RELOAD || Get_Clip_Rounds() == 0 ) {
			emitters_on = false;
		}
		Do_Continuous_Effects( emitters_on );
	}

	//
	//	Remember what our state last frame was...
	//
	LastFrameIsPrimaryTriggered	= IsPrimaryTriggered;
	LastFrameIsSecondaryTriggered	= IsSecondaryTriggered;
	return ;
}


/*
** Simulate firing straight down the muzzle and see where in the world we hit
*/
bool	WeaponClass::Cast_Weapon_Down_Muzzle( Vector3 & hit_pos )
{
	int muzzle_index	= Get_Total_Rounds_Fired() & 1;
	Matrix3D muzzle	= Get_Muzzle( muzzle_index );

	//
	//	Determine the start and end positions of the ray
	//
	Vector3 start	= muzzle.Get_Translation();
	Vector3 end		= muzzle * Vector3( Get_Range(), 0, 0 );
	LineSegClass ray( start, end );

	//
	//	Cast the ray into the world
	//
	CastResultStruct res;
	PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

	Ignore_Owner();
{ WWPROFILE( "Cast Ray" );
	COMBAT_SCENE->Cast_Ray( raytest );
}
	Unignore_Owner();

	//
	//	Calculate where in the world this would hit
	//
	bool retval = false;
	if ( res.StartBad == false ) {
		hit_pos = start + (end * res.Fraction);
		retval = true;
	}

	return retval;
}


/*
** Simulate firing from muzzle to target, see where and if we hit
*/
PhysicalGameObj * WeaponClass::Cast_Weapon( const Vector3 & target )
{
	int muzzle_index = Get_Total_Rounds_Fired() & 1;
	Matrix3D muzzle = Get_Muzzle( muzzle_index );

	Vector3 start = muzzle.Get_Translation();
//	Vector3 end = muzzle * Vector3( Get_Range(), 0, 0 );
	Vector3 end = target;
	LineSegClass ray( start, end );

	CastResultStruct res;
	PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

	Ignore_Owner();
{ WWPROFILE( "Cast Ray" );
	COMBAT_SCENE->Cast_Ray( raytest );
}
	Unignore_Owner();

//	Debug_Say(( "Weapon Focus %f %f %f\n", bullet_hit_position->X, bullet_hit_position->Y, bullet_hit_position->Z ));

	PhysicalGameObj * hit_obj = NULL;
	if ( raytest.CollidedPhysObj ) {
		if ( raytest.CollidedPhysObj->Get_Observer() != NULL ) {
			hit_obj = ((CombatPhysObserverClass *)raytest.CollidedPhysObj->Get_Observer())->As_PhysicalGameObj();
		}
	}

	return hit_obj;
}


/*
** Check to see if this weapon can reload
*/
bool
WeaponClass::Is_Reload_OK( void )
{
	bool retval = (InventoryRounds != 0);

	//
	//	Allow reloads if the infinite ammo cheat is enabled
	//
	if (	CheatMgrClass::Get_Instance () != NULL && 
			CheatMgrClass::Get_Instance ()->Is_Cheat_Enabled( CheatMgrClass::CHEAT_INFINITE_AMMO ) && Owner == COMBAT_STAR )
	{
		retval = true;
	}

	return retval;
}


void	WeaponClass::Stop_Firing_Sound( void )
{
	if ( FiringSound == NULL ) {
		return ;
	}

	//
	//	Stop the sound
	//
	FiringSound->Stop ();
	FiringSound->Remove_From_Scene ();
	REF_PTR_RELEASE (FiringSound);	
	return ;
}


// This function calcuates the weapon information that must be displayed for the star's
// weapon in the hud reticle & sniper view.  This should only be called for the weapon
// we want to show a reticle for.

// The goal is to find the HitPosition, and HitPositionHot

void	WeaponClass::Display_Targeting( void )
{
	WWPROFILE( "Cast_Star_Weapon" );
	int muzzle_index = Get_Total_Rounds_Fired() & 1;

	// vehicles that don't have a turret bone and do have homing weapons target from the camera
	Matrix3D muzzle;
	VehicleGameObj * vehicle = Get_Owner()->As_VehicleGameObj();;

	if (vehicle && (vehicle->Has_Turret() == false) && (PrimaryAmmoDefinition->IsTracking)) {
		muzzle = COMBAT_CAMERA->Get_Transform();
		muzzle.Rotate_Z( DEG_TO_RADF(90.0) );
		muzzle.Rotate_Y( DEG_TO_RADF(90.0) );
	} else {
		muzzle = Get_Muzzle( muzzle_index );
	}

	// Cast a ray to see what the weapon is pointing at
	Vector3 start = muzzle.Get_Translation();
	Vector3 end = muzzle * Vector3( Get_Range(), 0, 0 );
	LineSegClass ray( start, end );

	CastResultStruct res;
	PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);

	Ignore_Owner();

	{
		WWPROFILE( "Cast_Ray" );
		COMBAT_SCENE->Cast_Ray( raytest );
	}

	Unignore_Owner();

	Vector3	pos;
	ray.Compute_Point( raytest.Result->Fraction, &pos );
	HUDInfo::Set_Weapon_Target_Position( pos );

//	Debug_Say(( "Weapon Focus %f %f %f\n", bullet_hit_position->X, bullet_hit_position->Y, bullet_hit_position->Z ));

	if ( raytest.CollidedPhysObj && raytest.CollidedPhysObj->Get_Observer() != NULL ) {
		DamageableGameObj * obj = ((CombatPhysObserverClass *)raytest.CollidedPhysObj->Get_Observer())->As_DamageableGameObj();

		if ( obj->Is_Targetable() == false ) {
			obj = NULL;
		}

		// if stealthed and enemy, it's not targetable
		if ( obj && obj->As_SmartGameObj() && obj->As_SmartGameObj()->Is_Stealthed() && 
				COMBAT_STAR && obj->Is_Enemy( COMBAT_STAR ) ) {
			obj = NULL;
		}

		HUDInfo::Set_Weapon_Target_Object( obj );

		// We have a problem here with a building here clearing the PT, so dont set
		// buildings here, assume they will be set from the ccamera
		// Mo, try pulling back the cast above
		if ( obj == NULL || obj->As_BuildingGameObj() == NULL ) {
			HUDInfo::Set_Info_Object( obj );
		}
	}

}

#if 0
	// Aquire
	if ( AmmoDefinition->AquireTime != 0.0f ) {
		SmartGameObj *target = weapon->Pointing_At_Target();

		if ( target ) {
			if ( AquireTarget != target ) {	// locking onto someone new
				AquireTarget = target;
				AquireTimer = 0.0f;
			}
			AquireTimer += TimeManager::Get_Frame_Seconds();		// Bump the timer forward in seconds
			if (AquireTimer >= AmmoDefinition->AquireTime) {
				AquireTimer = AmmoDefinition->AquireTime;
			}
		} else {
			AquireTimer -= TimeManager::Get_Frame_Seconds();		// Bump the timer backwards in seconds
			if ( AquireTimer < 0.0f ) {
				AquireTimer = 0.0f;
			}
		}

		IsAquired = (target != NULL) &&
						(AquireTimer >= AmmoDefinition->AquireTime);
	}
#endif

const Matrix3D & WeaponClass::Get_Muzzle( int index )
{
	WWASSERT( Get_Owner() != NULL );

	// Star's weapon in first person fires from the camera
	if ( Get_Owner() == COMBAT_STAR && CombatManager::Is_First_Person() ) {
		static Matrix3D _muzzle;
		_muzzle.Obj_Look_At( COMBAT_CAMERA->Get_Transform().Get_Translation(), Get_Owner()->Get_Targeting_Pos(), 0 );
		return _muzzle;
	}

	return Get_Owner()->Get_Muzzle( index );
}


void	WeaponClass::Ignore_Owner( void )
{
	if ( Get_Owner() != NULL ) {
		if ( Get_Owner()->Peek_Physical_Object() != NULL ) {
			Get_Owner()->Peek_Physical_Object()->Inc_Ignore_Counter();
		}

		// If Owner is a vehicle, inc all the occupants
		if ( Get_Owner()->As_VehicleGameObj() != NULL ) {
			Get_Owner()->As_VehicleGameObj()->Ignore_Occupants();
		}
	}
}

void	WeaponClass::Unignore_Owner( void )
{
	if ( Get_Owner() != NULL ) {
		if ( Get_Owner()->Peek_Physical_Object() != NULL ) {
			Get_Owner()->Peek_Physical_Object()->Dec_Ignore_Counter();
		}

		if ( Get_Owner()->As_VehicleGameObj() != NULL ) {
			Get_Owner()->As_VehicleGameObj()->Unignore_Occupants();
		}
	}
}


/*
**
*/
MuzzleFlashClass::MuzzleFlashClass( void ) :
	MuzzleA0Bone( 0 ),
	MuzzleA1Bone( 0 ),
	Rotation( 0 ),
	Model( 0 ),
	LastFlashA0( true ),
	LastFlashA1( true )
{
}

MuzzleFlashClass::~MuzzleFlashClass( void )
{
	REF_PTR_RELEASE( Model );
}

void	MuzzleFlashClass::Init( RenderObjClass * model )
{
	REF_PTR_SET( Model, model );

	if ( model != NULL ) {

		// Find the muzzle bone ( for rotation )
		MuzzleA0Bone = model->Get_Bone_Index( "muzzlea0" );
		MuzzleA1Bone = model->Get_Bone_Index( "muzzlea1" );
		if ( MuzzleA0Bone > 0 ) {
//			model->Capture_Bone( MuzzleA0Bone );
		}
		if ( MuzzleA1Bone > 0 ) {
//			model->Capture_Bone( MuzzleA1Bone );
		}

		Update( false, false );
	}

}

void	MuzzleFlashClass::Update( bool flashA0, bool flashA1 )
{
	if ( MuzzleA1Bone == 0 ) {
		flashA0 = flashA0 | flashA1;
		flashA1 = false;
	}

	if ( Model != NULL ) {
		if (( MuzzleA0Bone > 0 ) && (LastFlashA0 != flashA0)) {
			LastFlashA0 = flashA0;
	#if 0
			if ( flash ) {
				Rotation += TimeManager::Get_Frame_Seconds() * 10;
				Matrix3D tm(1);
				tm.Rotate_X( Rotation );
				Model->Control_Bone( MuzzleA0Bone, tm );
			}
	#endif

			for (int i=0; i<Model->Get_Num_Sub_Objects_On_Bone( MuzzleA0Bone ); i++) {
				RenderObjClass * robj = Model->Get_Sub_Object_On_Bone(i, MuzzleA0Bone );
					
				// hide all meshes named "muzzleflash"
				if (strstr(robj->Get_Name(),"MUZZLEFLASH") || strstr(robj->Get_Name(),"MZ"))  {
					robj->Set_Hidden( !flashA0 );
				}

				robj->Release_Ref();
			}
		}

		if (( MuzzleA1Bone > 0 ) && (LastFlashA1 != flashA1)) {
			
			LastFlashA1 = flashA1;
	#if 0
			if ( flash ) {
				Rotation += TimeManager::Get_Frame_Seconds() * 10;
				Matrix3D tm(1);
				tm.Rotate_X( Rotation );
				Model->Control_Bone( MuzzleA1Bone, tm );
			}
	#endif

			for (int i=0; i<Model->Get_Num_Sub_Objects_On_Bone( MuzzleA1Bone ); i++) {
				RenderObjClass * robj = Model->Get_Sub_Object_On_Bone(i, MuzzleA1Bone );
				// hide all meshes named "muzzleflash"
				if (strstr(robj->Get_Name(),"MUZZLEFLASH") || strstr(robj->Get_Name(),"MZ")) {
					robj->Set_Hidden( !flashA1 );
				}
				robj->Release_Ref();
			}
		}
	}
}
