/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
$Id$
$DateTime$

-------------------------------------------------------------------------
History:
- 11:9:2005   15:00 : Created by Márcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "Single.h"
#include "Item.h"
#include "Weapon.h"
#include "Projectile.h"
#include "Actor.h"
#include "Player.h"
#include "Game.h"
#include "WeaponSystem.h"
#include <IEntitySystem.h>
#include "ISound.h"
#include <IVehicleSystem.h>

#include "IRenderer.h"
#include "IRenderAuxGeom.h"	


//---------------------------------------------------------------------------
// TODO remove when aiming/fire direction is working
// debugging aiming dir
struct DBG_shoot{
	Vec3	src;
	Vec3	dst;
};

const	int	DGB_ShotCounter(3);
int	DGB_curIdx(-1);
int	DGB_curLimit(-1);
DBG_shoot DGB_shots[DGB_ShotCounter];
// remove over
//---------------------------------------------------------------------------



//------------------------------------------------------------------------
CSingle::CSingle()
: m_pWeapon(0),
	m_projectileId(0),
	m_enabled(true),
	m_ammoid(0),
	m_mfTimer(0.0f),
	m_mflTimer(0.0f),
	m_zoomtimeout(0.0f),
	m_bLocked(false),
	m_fStareTime(0.0f),
	m_lockedTarget(0)
{
}

//------------------------------------------------------------------------
CSingle::~CSingle()
{
	ClearTracerCache();
}

//------------------------------------------------------------------------
void CSingle::Init(IWeapon *pWeapon, const IItemParamsNode *params)
{
	m_pWeapon = static_cast<CWeapon *>(pWeapon);

	if (params)
		ResetParams(params);

	CacheTracer();
}

//------------------------------------------------------------------------
void CSingle::Update(float frameTime, uint frameId)
{
  FUNCTION_PROFILER( GetISystem(), PROFILE_GAME );

	if (m_fireparams.autoaim)
		UpdateAutoAim(frameTime);

	if (m_zoomtimeout > 0.0f && m_fireparams.autozoom)
	{
		m_zoomtimeout -= frameTime;
		ICVar *zoomouttime = GetISystem()->GetIConsole()->GetCVar("int_ZoomOutTime");
		ICVar *zoomamount = GetISystem()->GetIConsole()->GetCVar("int_ZoomAmount");
		if (m_zoomtimeout < 0.0f)
		{
			m_zoomtimeout = 0.0f;
			CActor *pActor = m_pWeapon->GetOwnerActor();
			if (pActor)
				pActor->GetIntensityMonitor()->ChangeFOV(1.0f, zoomouttime->GetFVal(), true, 1.0f - zoomamount->GetFVal());
		}
		else
		{
			if (m_firing)
			{
				ICVar *zoomintime = GetISystem()->GetIConsole()->GetCVar("int_ZoomInTime");
				CActor *pActor = m_pWeapon->GetOwnerActor();
				if (pActor)
					pActor->GetIntensityMonitor()->ChangeFOV(zoomamount->GetFVal(), zoomintime->GetFVal(), false, zoomamount->GetFVal() - 1.0f);
			}
		}
	}
	if (m_spinUpTime>0.0f)
	{
		m_spinUpTime -= frameTime;
		if (m_spinUpTime<=0.0f)
		{
			m_spinUpTime=0.0f;
			Shoot(true);
		}
	}
	else
	{
		if (m_next_shot>0.0f)
		{
			m_next_shot -= frameTime;
			if (m_next_shot<=0.0f)
				m_next_shot=0.0f;
		}
	}

	if (IsReadyToFire())
		m_pWeapon->OnReadyToFire();

	// update muzzle flash effect
	if (m_mfTimer>0.0f)
	{
		m_mfTimer -= frameTime;
		if (m_mfTimer <= 0.0f)
		{
			m_mfTimer = 0.0f;
			if (m_mfId)
				MuzzleFlashEffect(false, false, true);
		}
	}

	// update muzzle flash light
	if (m_mflTimer>0.0f)
	{
		m_mflTimer -= frameTime;
		if (m_mflTimer <= 0.0f)
		{
			m_mflTimer = 0.0f;
			if (m_mflightId)
				MuzzleFlashEffect(false, true, false);
		}
	}

	// update spinup effect
	if (m_suTimer>0.0f)
	{
		m_suTimer -= frameTime;
		if (m_suTimer <= 0.0f)
		{
			m_suTimer = 0.0f;
			if (m_suId)
				SpinUpEffect(false);
		}
	}

	UpdateRecoil(frameTime);
	m_fired = false;

	//---------------------------------------------------------------------------
	// TODO remove when aiming/fire direction is working
	// debugging aiming dir
	if(GetISystem()->GetIConsole()->GetCVar("g_aimdebug") && GetISystem()->GetIConsole()->GetCVar("g_aimdebug")->GetIVal()!=0)
	{
		const ColorF	queueFireCol( .4f, 1.0f, 0.4f, 1.0f );
		for(int dbgIdx(0);dbgIdx<DGB_curLimit; ++dbgIdx)
			GetISystem()->GetIRenderer()->GetIRenderAuxGeom()->DrawLine( DGB_shots[dbgIdx].src, queueFireCol, DGB_shots[dbgIdx].dst, queueFireCol );
	}
	//---------------------------------------------------------------------------
}


//------------------------------------------------------------------------
bool CSingle::IsValidAutoAimTarget(IEntity* pEntity)
{  
  IActor *pActor = 0;				
  IVehicle* pVehicle = 0;
  
  if (pEntity->IsHidden())
    return false;

  AABB box;
  pEntity->GetLocalBounds(box);
  float vol = box.GetVolume();  
  
  if (vol < m_fireparams.autoaim_minvolume || vol > m_fireparams.autoaim_maxvolume)
  {
    //CryLogAlways("volume check failed: %f", vol);
    return false;
  }
  
  pActor = GetISystem()->GetIGame()->GetIGameFramework()->GetIActorSystem()->GetActor(pEntity->GetId());
  if (pActor && pActor->GetHealth() > 0.f)
    return true;
  
  pVehicle = GetISystem()->GetIGame()->GetIGameFramework()->GetIVehicleSystem()->GetVehicle(pEntity->GetId()); 
  if (pVehicle && pVehicle->GetStatus().health > 0.f)
    return true;
    
  return false;
}

//------------------------------------------------------------------------
bool CSingle::CheckAutoAimTolerance(const Vec3& aimPos, const Vec3& aimDir)
{
  // todo: this check is probably not sufficient
  IEntity *pLocked = GetISystem()->GetIEntitySystem()->GetEntity(m_lockedTarget);
  AABB bbox;
  pLocked->GetWorldBounds(bbox);
  Vec3 targetPos = bbox.GetCenter();
  Vec3 dirToTarget = (targetPos - aimPos).normalize();
  float dot = aimDir.Dot(dirToTarget);
  Matrix33 mat = Matrix33::CreateRotationVDir(dirToTarget);
  Vec3 right = mat.GetColumn(0).normalize();
  Vec3 maxVec = (targetPos - aimPos) + (right * m_fireparams.autoaim_tolerance);
  float maxDot = dirToTarget.Dot(maxVec.normalize());
  
  return (dot >= maxDot);
}

//------------------------------------------------------------------------
void CSingle::UpdateAutoAim(float frameTime)
{
  static IGameObjectSystem* pGameObjectSystem = GetISystem()->GetIGame()->GetIGameFramework()->GetIGameObjectSystem();

	CActor *owner = m_pWeapon->GetOwnerActor();
  if (!owner || !owner->IsPlayer())
    return;
	
  // todo: use crosshair/aiming dir
  CCamera &cam = GetISystem()->GetViewCamera();
	Vec3 aimDir = cam.GetViewdir();
	Vec3 aimPos = cam.GetPosition();
	
	float maxDistance = m_fireparams.autoaim_distance;
  
  ray_hit ray;
  int nSkipEnts = 1;
  IPhysicalEntity* pSkipEnts[2];
  pSkipEnts[0] = owner->GetEntity()->GetPhysics();

  if (owner->GetLinkedEntity())
  {
    pSkipEnts[1] = owner->GetLinkedEntity()->GetPhysics();
    ++nSkipEnts;
  }
	
	int result = GetISystem()->GetIPhysicalWorld()->RayWorldIntersection(aimPos, aimDir * 2.f * maxDistance, 
		ent_all & ~(ent_terrain|ent_static), rwi_stop_at_pierceable|rwi_colltype_any|rwi_any_hit, &ray, 1, pSkipEnts, nSkipEnts);		

  bool hitValidTarget = false;
  IEntity* pEntity = 0;

  if (result && ray.pCollider)
	{	
		pEntity = (IEntity *)ray.pCollider->GetForeignData(PHYS_FOREIGN_ID_ENTITY);    	        
    if (pEntity && IsValidAutoAimTarget(pEntity))
      hitValidTarget = true;
  }

  if (hitValidTarget && ray.dist <= maxDistance)
  {	
    if (m_bLocked)
		{
			if (m_lockedTarget != pEntity->GetId())
			{
        // switch lock (is this really intended?)
				//CryLogAlways("pent with id: %d does not match locked: %d", pEntity->GetId(), m_lockedTarget);
				m_lockedTarget = pEntity->GetId();
				
        if (owner->IsClient())
        {
          SGameObjectEvent evt("HUD_AutoaimLocked",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
          pGameObjectSystem->BroadcastEvent(evt);
        }
			}
		}
		else
		{	
      // start locking
			m_lockedTarget = pEntity->GetId();
			if (m_fStareTime == 0.0f && owner->IsClient())
			{
				SGameObjectEvent evt("HUD_AutoaimLocking",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
				pGameObjectSystem->BroadcastEvent(evt);
			}
			m_fStareTime += frameTime;						
		}
	}
	else
	{
		// check if we're looking far away from our locked target
		if ((m_bLocked && !(ray.dist<=maxDistance && CheckAutoAimTolerance(aimPos, aimDir))) || (!m_bLocked && m_lockedTarget != 0 && m_fStareTime != 0.0f))
    { 
      //CryLogAlways("Lost lock");          
      if (owner->IsClient())
      {
        SGameObjectEvent evt("HUD_AutoaimUnlock",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
        pGameObjectSystem->BroadcastEvent(evt);
      }							
      m_lockedTarget = 0;
      m_fStareTime = 0.0f;
      m_bLocked = false;
    }
	}		
	
  if (m_bLocked == false && m_fStareTime >= m_fireparams.autoaim_locktime && m_lockedTarget != 0)
	{
    // lock
		m_bLocked = true;
		ISoundSystem *sound = GetISystem()->GetISoundSystem();
		if (sound && owner->IsClient())
		{			
			_smart_ptr< ISound > beep = GetISystem()->GetISoundSystem()->CreateSound("Sounds/interface:hud:target_lock", FLAG_SOUND_2D);
			if (beep)
				beep->Play();
			
			SGameObjectEvent evt("HUD_AutoaimLocked",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
			pGameObjectSystem->BroadcastEvent(evt);
		}
		//CryLogAlways("Locked!");
	}	
  else if (m_bLocked)
	{ 
    // check if target still valid (can e.g. be killed)
    IEntity* pEntity = GetISystem()->GetIEntitySystem()->GetEntity(m_lockedTarget);	
		if (pEntity && !IsValidAutoAimTarget(pEntity))
		{ 
      if (owner->IsClient())
      {
			  SGameObjectEvent evt("HUD_AutoaimUnlock",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
			  pGameObjectSystem->BroadcastEvent(evt);
      }
			m_lockedTarget = 0;
      m_bLocked = false;
      m_fStareTime = 0.0f;
		}
	}
}
//------------------------------------------------------------------------
void CSingle::Destroy()
{
}

//------------------------------------------------------------------------
void CSingle::ResetParams(const IItemParamsNode *params)
{
	const IItemParamsNode *fire = params?params->GetChild("fire"):0;
	const IItemParamsNode *tracer = params?params->GetChild("tracer"):0;
	const IItemParamsNode *ooatracer = params?params->GetChild("outofammotracer"):0;
	const IItemParamsNode *recoil = params?params->GetChild("recoil"):0;
	const IItemParamsNode *spread = params?params->GetChild("spread"):0;
	const IItemParamsNode *actions = params?params->GetChild("actions"):0;
	const IItemParamsNode *muzzleflash = params?params->GetChild("muzzleflash"):0;
	const IItemParamsNode *reject = params?params->GetChild("reject"):0;
	const IItemParamsNode *spinup = params?params->GetChild("spinup"):0;

	m_fireparams.Reset(fire);
	m_tracerparams.Reset(tracer);
	m_ooatracerparams.Reset(ooatracer);
	m_recoilparams.Reset(recoil);
	m_spreadparams.Reset(spread);
	m_actions.Reset(actions);
	m_muzzleflash.Reset(muzzleflash);
	m_reject.Reset(reject);
	m_spinup.Reset(spinup);
}

//------------------------------------------------------------------------
void CSingle::PatchParams(const IItemParamsNode *patch)
{
	const IItemParamsNode *fire = patch->GetChild("fire");
	const IItemParamsNode *tracer = patch->GetChild("tracer");
	const IItemParamsNode *ooatracer = patch->GetChild("outofammotracer");
	const IItemParamsNode *recoil = patch->GetChild("recoil");
	const IItemParamsNode *spread = patch->GetChild("spread");
	const IItemParamsNode *actions = patch->GetChild("actions");
	const IItemParamsNode *muzzleflash = patch->GetChild("muzzleflash");
	const IItemParamsNode *reject = patch->GetChild("reject");
	const IItemParamsNode *spinup = patch->GetChild("spinup");

	m_fireparams.Reset(fire, false);
	m_tracerparams.Reset(tracer, false);
	m_ooatracerparams.Reset(ooatracer, false);
	Activate(true);
	m_recoilparams.Reset(recoil, false);
	m_spreadparams.Reset(spread, false);
	m_actions.Reset(actions, false);
	m_muzzleflash.Reset(muzzleflash, false);
	m_reject.Reset(reject, false);
	m_spinup.Reset(spinup, false);
}

//------------------------------------------------------------------------
void CSingle::Activate(bool activate)
{
	m_fired = m_firing = m_reloading = m_emptyclip = false;
	m_spinUpTime = 0.0f;
	m_next_shot = 0.0f;
	m_next_shot_dt = 60.0f/m_fireparams.rate;

	MuzzleFlashEffect(false);
	SpinUpEffect(false);

	if (m_bLocked)
	{
		SGameObjectEvent evt("HUD_AutoaimUnlock",eGOEF_ToAll, IGameObjectSystem::InvalidExtensionID, (void *)(UINT_PTR)m_lockedTarget);
		GetISystem()->GetIGame()->GetIGameFramework()->GetIGameObjectSystem()->BroadcastEvent(evt);
		m_bLocked = false;
		m_lockedTarget = 0;
	}
  m_fStareTime = 0.f;

	ResetRecoil();
}

//------------------------------------------------------------------------
int CSingle::GetAmmoCount() const
{
	return m_pWeapon->GetAmmoCount(GetAmmoType());
}

//------------------------------------------------------------------------
int CSingle::GetClipSize() const
{
	return m_fireparams.clip_size;
}

//------------------------------------------------------------------------
int CSingle::GetClipCount() const
{
	return m_pWeapon->GetClipCount(GetAmmoType());
}

//------------------------------------------------------------------------
bool CSingle::OutOfAmmo() const
{
	return m_pWeapon->GetAmmoCount(m_fireparams.ammo_type)<1;
}

//------------------------------------------------------------------------
bool CSingle::CanReload() const
{
	return !m_reloading && (m_pWeapon->GetClipCount(m_fireparams.ammo_type.c_str())>0);
}

bool CSingle::IsReloading()
{
	return m_reloading;
}

//------------------------------------------------------------------------
void CSingle::Reload(bool zoomed)
{
	StartReload(zoomed);
}

//------------------------------------------------------------------------
bool CSingle::CanFire(bool considerAmmo) const
{
	return !m_reloading && (m_next_shot<=0.0f) && (m_spinUpTime<=0.0f) &&
		!m_pWeapon->IsBusy() && (!considerAmmo || !OutOfAmmo());
}

//------------------------------------------------------------------------
void CSingle::StartFire(EntityId shooterId)
{
	if (m_pWeapon->IsBusy())
		return;

	m_shooterId = shooterId;
	if (m_fireparams.spin_up_time>0.0f)
	{
		m_firing = true;
		m_spinUpTime = m_fireparams.spin_up_time;

		m_pWeapon->PlayAction(m_actions.spin_up.c_str());
		SpinUpEffect(true);
	}
	else
		m_firing = Shoot(true);
}

//------------------------------------------------------------------------
void CSingle::StopFire(EntityId shooterId)
{
	m_firing = false;
}


//------------------------------------------------------------------------
EntityId CSingle::GetProjectileId() const
{
	return m_projectileId;
}

//------------------------------------------------------------------------
const char *CSingle::GetType() const
{
	return "Single";
}

//------------------------------------------------------------------------
const char *CSingle::GetAmmoType() const
{
	return m_fireparams.ammo_type.c_str();
}

//------------------------------------------------------------------------
float CSingle::GetSpinUpTime() const
{
	return m_fireparams.spin_up_time;
}

//------------------------------------------------------------------------
float CSingle::GetSpinDownTime() const
{
	return m_fireparams.spin_down_time;
}

//------------------------------------------------------------------------
void CSingle::Enable(bool enable)
{
	m_enabled = enable;
}

//------------------------------------------------------------------------
bool CSingle::IsEnabled() const
{
	return m_enabled;
}

//------------------------------------------------------------------------
void CSingle::StartReload(bool zoomed)
{
	CActor *pActor = m_pWeapon->GetOwnerActor();
	m_reloading = true;
	if (zoomed)
		m_pWeapon->ExitZoom();
	m_pWeapon->SetBusy(true);

	const char *action = m_actions.reload.c_str();
	const char *ammo = m_fireparams.ammo_type.c_str();

	if (m_fireparams.bullet_chamber)
	{
		int ammoCount = m_pWeapon->GetAmmoCount(ammo);
		if ((ammoCount>0) && (ammoCount < m_fireparams.clip_size))
			action = m_actions.reload_chamber_full.c_str();
		else
			action = m_actions.reload_chamber_empty.c_str();
	}

	m_pWeapon->PlayAction(action);

	struct EndReloadAction: public ISchedulerAction
	{
		EndReloadAction(CSingle *_single, bool zoomed): single(_single), _zoomed(zoomed){};
		CSingle *single;
		bool _zoomed;

		virtual void execute(CItem *_this)
		{
			single->EndReload(_zoomed);
		}
	};
	m_pWeapon->GetScheduler()->TimerAction((uint)(m_fireparams.reload_time*1000), new EndReloadAction(this, zoomed), false);

	struct SliderBack: public ISchedulerAction
	{
		SliderBack(CSingle *_single): single(_single) {};
		CSingle *single;

		virtual void execute(CItem *_this)
		{
			_this->StopLayer(single->m_fireparams.slider_layer.c_str());
		}
	};
	m_pWeapon->GetScheduler()->TimerAction((uint)((m_fireparams.reload_time-0.125f)*1000), new SliderBack(this), false);
}

//------------------------------------------------------------------------
void CSingle::EndReload(bool zoomed)
{
	m_reloading = false;
	m_emptyclip = false;
	m_ammoid = 0;
	m_spinUpTime = m_firing?m_fireparams.spin_up_time:0.0f;

	const char *ammo = m_fireparams.ammo_type.c_str();

	int ammoCount = m_pWeapon->GetAmmoCount(ammo);
	if (m_fireparams.bullet_chamber && (ammoCount>0) && (ammoCount<m_fireparams.clip_size))
		ammoCount = m_fireparams.clip_size+1;
	else
		ammoCount = m_fireparams.clip_size;

	m_pWeapon->SetAmmoCount(ammo, ammoCount);
	
	if (CItem::i_unlimitedammo->GetIVal() == 0)
		m_pWeapon->SetClipCount(ammo, m_pWeapon->GetClipCount(ammo)-1);

	m_pWeapon->SetBusy(false);
	if (zoomed)
		m_pWeapon->StartZoom(m_pWeapon->GetOwnerId());

}

//------------------------------------------------------------------------
bool CSingle::Shoot(bool resetAnimation, bool autoreload)
{
	const char *ammo = m_fireparams.ammo_type.c_str();
	int ammoCount = m_pWeapon->GetAmmoCount(ammo);

	if (!CanFire(true))
	{
		if ((ammoCount <= 0) && (!m_reloading))
		{
			/*if (!m_emptyclip)
			{
				m_emptyclip = true;
				m_pWeapon->PlayAction(m_actions.empty_clip.c_str());

				return true;
			}
			else if (CanReload())
			{
				m_pWeapon->PlayAction(m_actions.empty_clip.c_str());
				m_pWeapon->Reload();

				return true;
			}
			*/
		}

		return false;
	}

	const char *action = m_actions.fire_cock.c_str();
	if (ammoCount == 1)
		action = m_actions.fire.c_str();

	m_pWeapon->PlayAction(action, 0, false, CItem::eIPAF_Default|CItem::eIPAF_NoBlend|CItem::eIPAF_RestartAnimation);

	Vec3 hit = GetProbableHit(WEAPON_HIT_RANGE);
	Vec3 pos = GetFiringPos(hit);
	Vec3 dir = ApplySpread(GetFiringDir(hit));
	Vec3 vel = GetFiringVelocity();
	Vec3 tracerhit = ZERO;
	ray_hit rayhit;

	if (!m_pWeapon->GetOwner())	
		return(false); // [marco] should never happen? Tony got a crash here

	int intersect = GetISystem()->GetIPhysicalWorld()->RayWorldIntersection(pos, dir * WEAPON_HIT_RANGE, ent_all,
		rwi_stop_at_pierceable|rwi_colltype_any|rwi_any_hit, &rayhit, 1, m_pWeapon->GetOwner()?m_pWeapon->GetOwner()->GetPhysics():0);
	if (intersect)
		tracerhit = rayhit.pt;
	else
		tracerhit = pos + dir * WEAPON_HIT_RANGE;

	// SHOT HERE
	CProjectile *pAmmo = m_pWeapon->SpawnAmmo(ammo);
	if (pAmmo)
	{
		pAmmo->SetParams(m_shooterId, m_pWeapon->GetEntityId(), m_fireparams.damage);    
    
    if (m_bLocked)
      pAmmo->SetDestination(m_lockedTarget);
    else
      pAmmo->SetDestination(m_pWeapon->GetDestination());

    pAmmo->Launch(pos, dir, vel);
		CActor *pDizzle = m_pWeapon->GetOwnerActor();
		if (pDizzle && pDizzle->IsPlayer())
		{
			CPlayer *player = (CPlayer *)pDizzle;
			if (player->IsZeroG())
			{
				IEntityPhysicalProxy *pPhysicsProxy = (IEntityPhysicalProxy*)player->GetEntity()->GetProxy(ENTITY_PROXY_PHYSICS);
				SMovementState ms;
				player->GetMovementController()->GetMovementState(ms);
				Vec3 impulseDir = ms.aimDirection * -1.0f;
				Vec3 impulsePos = ms.pos;
				
				pPhysicsProxy->AddImpulse(-1, impulsePos, impulseDir * 10.0f, true, 1.0f);
			}
		}

		bool emit = (!m_tracerparams.geometry.empty() || !m_tracerparams.effect.empty()) && (!m_ammoid || (m_ammoid%m_tracerparams.frequency==0));
		bool ooa = ((m_fireparams.ooatracer_treshold>0) && m_fireparams.ooatracer_treshold>=ammoCount);

		if (emit || ooa)
		{
			CTracerPath path;
			path.AddPoint(tracerhit);

			CTracerManager::STracerParams params;
			params.path = &path;
			params.position = pos;

			if (ooa)
			{
				params.geometry = m_ooatracerparams.geometry.c_str();
				params.effect = m_ooatracerparams.effect.c_str();
				params.scale = m_ooatracerparams.scale;
				params.speed = m_ooatracerparams.speed;
				params.lifetime = m_ooatracerparams.lifetime;
			}
			else
			{
				params.geometry = m_tracerparams.geometry.c_str();
				params.effect = m_tracerparams.effect.c_str();
				params.scale = m_tracerparams.scale;
				params.speed = m_tracerparams.speed;
				params.lifetime = m_tracerparams.lifetime;
			}

			path.First();
			g_pGame->GetWeaponSystem()->GetTracerManager().EmitTracer(params);
		}

		m_projectileId = pAmmo->GetEntity()->GetId();
		++m_ammoid;
	}

	m_pWeapon->OnShoot(m_shooterId, pAmmo?pAmmo->GetEntity()->GetId():0, ammo, pos, dir, vel);

	MuzzleFlashEffect(true);
	RejectEffect();
	
	m_fired = true;
	m_next_shot += m_next_shot_dt;
	m_zoomtimeout = m_next_shot + 0.5f;
	ammoCount--;

	m_pWeapon->SetAmmoCount(ammo, ammoCount);
	if ((ammoCount<1) && !m_fireparams.slider_layer.empty())
	{
		const char *slider_back_layer = m_fireparams.slider_layer.c_str();
		m_pWeapon->PlayLayer(slider_back_layer, CItem::eIPAF_Default|CItem::eIPAF_NoBlend);
	}

	if (OutOfAmmo())
	{
		m_pWeapon->OnOutOfAmmo(ammo);
		if (autoreload)
		{
			class ScheduleReload : public ISchedulerAction {
			public:
				ScheduleReload(CWeapon *wep)
				{
					_pWeapon = wep;
				}
				virtual ~ScheduleReload()
				{

				}
				virtual void execute(CItem *item) {
					_pWeapon->Reload();
					//delete this;
				}
			private:
				CWeapon *_pWeapon;
			};
			m_pWeapon->GetScheduler()->TimerAction(m_pWeapon->GetCurrentAnimationTime(CItem::eIGS_FirstPerson), new ScheduleReload(m_pWeapon), false);
		}
	}

	//---------------------------------------------------------------------------
	// TODO remove when aiming/fire direction is working
	// debugging aiming dir
	if(++DGB_curLimit>DGB_ShotCounter)	DGB_curLimit = DGB_ShotCounter;
	if(++DGB_curIdx>=DGB_ShotCounter)	DGB_curIdx = 0;
	DGB_shots[DGB_curIdx].dst=pos+dir*50;
	DGB_shots[DGB_curIdx].src=pos;
	//---------------------------------------------------------------------------

	if (m_pWeapon->GetOwnerActor() && m_pWeapon->GetOwnerActor()->IsPlayer())
		m_pWeapon->GetGameObject()->InvokeRMI(CWeapon::SvRequestShoot(), CWeapon::RequestShootParams(m_shooterId, hit), eRMI_ToServer);

	return true;
}

//------------------------------------------------------------------------
bool CSingle::ShootFromHelper(const Vec3 &eyepos, const Vec3 &probableHit)
{
	Vec3 dp(eyepos-probableHit);
	return dp.len2()>(WEAPON_HIT_MIN_DISTANCE*WEAPON_HIT_MIN_DISTANCE);
}

//------------------------------------------------------------------------
Vec3 CSingle::GetProbableHit(float range, bool *pbHit, ray_hit *pHit)
{
  static Vec3 pos,dir;  
  CActor *pActor = m_pWeapon->GetOwnerActor();
  IPhysicalEntity *pSkipEntity = pActor ? pActor->GetEntity()->GetPhysics() : 0;

  IWeaponFiringLocator *pLocator = m_pWeapon->GetFiringLocator();
  if (pLocator)
  {
    pos = pLocator->GetFiringPos(m_pWeapon->GetEntityId());
    dir = range * pLocator->GetFiringDir(m_pWeapon->GetEntityId());
  }
  else
  {    
    IMovementController * pMC = pActor ? pActor->GetMovementController() : 0;
    if (pMC)
    { 
      SMovementState info;
      pMC->GetMovementState(info);
      pos = info.handPosition;
      dir = info.aimDirection*range;
    }
    else
    {
      //assert(!"should never happen...");
      return Vec3(0,0,0);
    }
  }

	static ray_hit hit;	
	if (GetISystem()->GetIPhysicalWorld()->RayWorldIntersection(pos, dir, ent_all,
		rwi_stop_at_pierceable|rwi_ignore_back_faces, &hit, 1, pSkipEntity))
	{
		if (pbHit)
			*pbHit=true;
		if (pHit)
			*pHit=hit;
		return hit.pt;
	}

	if (pbHit)
		*pbHit=false;

	return pos+dir;
}

//------------------------------------------------------------------------
Vec3 CSingle::GetFiringPos(const Vec3 &probableHit)
{
	IWeaponFiringLocator *pLocator = m_pWeapon->GetFiringLocator();
	if (pLocator)
		return pLocator->GetFiringPos(m_pWeapon->GetEntityId());

	Vec3 pos(0,0,0);

	CActor *pActor = m_pWeapon->GetOwnerActor();
	IMovementController * pMC = pActor ? pActor->GetMovementController() : 0;
	if (pMC)
	{
		SMovementState info;
		pMC->GetMovementState(info);

		pos = info.handPosition;

		if (!m_fireparams.helper.empty() && ShootFromHelper(pos, probableHit))
		{
			if (m_pWeapon->GetStats().fp)
				pos = m_pWeapon->GetSlotHelperPos(CItem::eIGS_FirstPerson, m_fireparams.helper.c_str(), true);
			else
				pos = m_pWeapon->GetSlotHelperPos(CItem::eIGS_ThirdPerson, m_fireparams.helper.c_str(), true);
		}
	}

	return pos;
}

//------------------------------------------------------------------------
Vec3 CSingle::GetFiringDir(const Vec3 &probableHit)
{
	if (m_fireparams.autoaim && m_fireparams.autoaim_autofiringdir)
	{
		if (m_bLocked)
		{
			IEntity *pEnt = GetISystem()->GetIEntitySystem()->GetEntity(m_lockedTarget);
			if (pEnt)
			{
				AABB bbox;
				pEnt->GetWorldBounds(bbox);
				Vec3 center = bbox.GetCenter();
				IActor *pAct = GetISystem()->GetIGame()->GetIGameFramework()->GetIActorSystem()->GetActor(m_lockedTarget);
				if (pAct)
				{
					if (IMovementController *pMV = pAct->GetMovementController())
					{
						SMovementState ms;
						pMV->GetMovementState(ms);
						center = ms.eyePosition;
					}
				}
				Vec3 aimpos = GetFiringPos(probableHit);
				Vec3 dir = (center - aimpos).normalize();
				return dir;
			}
		}
	}
	
	IWeaponFiringLocator *pLocator = m_pWeapon->GetFiringLocator();
	if (pLocator)
		return pLocator->GetFiringDir(m_pWeapon->GetEntityId());

	Vec3 dir = FORWARD_DIRECTION;

	CActor *pActor = m_pWeapon->GetOwnerActor();
	IMovementController * pMC = pActor ? pActor->GetMovementController() : 0;
	if (pMC)
	{
		SMovementState info;
		pMC->GetMovementState(info);

		dir = info.aimDirection;

		if (!m_fireparams.helper.empty() && ShootFromHelper(info.handPosition, probableHit))
		{
			Vec3 pos = GetFiringPos(probableHit);
			dir = (probableHit-pos).normalized();
		}
	}

	return dir;
}

//------------------------------------------------------------------------
Vec3 CSingle::GetFiringVelocity()
{
	IWeaponFiringLocator *pLocator = m_pWeapon->GetFiringLocator();
	if (pLocator)
		return pLocator->GetFiringVelocity(m_pWeapon->GetEntityId());


	return Vec3(0,0,0);
}

//------------------------------------------------------------------------
Vec3 CSingle::ApplySpread(const Vec3 &dir)
{
	return dir;
}

//------------------------------------------------------------------------
void CSingle::MuzzleFlashEffect(bool attach, bool light, bool effect)
{
	if (effect)
	{
		m_pWeapon->AttachEffect(0, m_mfId, false);
		m_mfId=0;
	}
	if (light)
	{
		m_pWeapon->AttachLight(0, m_mflightId, false);
		m_mflightId=0;
	};

	if (attach)
	{
		int slot = m_pWeapon->GetStats().fp ? CItem::eIGS_FirstPerson : CItem::eIGS_ThirdPerson;
		int id = m_pWeapon->GetStats().fp ? 0 : 1;

    Vec3 offset(ZERO), dir(1,0,0);

		if (!m_muzzleflash.effect[id].empty() && effect)
		{
      if (IWeaponFiringLocator *pLocator = m_pWeapon->GetFiringLocator())
      {
        dir = pLocator->GetMuzzleDirLocal(m_pWeapon->GetEntityId());
      }

			m_mfId = m_pWeapon->AttachEffect(slot, 0, true, m_muzzleflash.effect[id].c_str(), 
				m_muzzleflash.helper[id].c_str(), offset, dir);

			m_mfTimer = m_muzzleflash.time[id];
		}

		if (!m_muzzleflash.effect[id].empty() && light)
		{
			m_mflightId = m_pWeapon->AttachLight(slot, 0, true, m_muzzleflash.light_radius[id], m_muzzleflash.light_color[id], Vec3(1,1,1), 0, 0,
				m_muzzleflash.light_helper[id].c_str());

			m_mflTimer = m_muzzleflash.light_time[id];
		}
	}
}

//------------------------------------------------------------------------
void CSingle::SpinUpEffect(bool attach)
{
	m_pWeapon->AttachEffect(0, m_suId, false);
	m_pWeapon->AttachLight(0, m_sulightId, false);
	m_suId=0;
	m_sulightId=0;

	if (attach)
	{
		int slot = m_pWeapon->GetStats().fp ? CItem::eIGS_FirstPerson : CItem::eIGS_ThirdPerson;
		int id = m_pWeapon->GetStats().fp ? 0 : 1;

		m_suId = m_pWeapon->AttachEffect(slot, 0, true, m_spinup.effect[id].c_str(), 
			m_spinup.helper[id].c_str(), Vec3(0,0,0), Vec3(0,1,0), 1.0f, false);

		m_sulightId = m_pWeapon->AttachLight(slot, 0, true, m_spinup.light_radius[id], m_spinup.light_color[id], Vec3(1,1,1), 0, 0,
			m_spinup.light_helper[id].c_str());

		m_suTimer = (uint)(m_spinup.time[id]);
	}
}

//------------------------------------------------------------------------
void CSingle::RejectEffect()
{
	int slot = m_pWeapon->GetStats().fp ? CItem::eIGS_FirstPerson : CItem::eIGS_ThirdPerson;
	int id = m_pWeapon->GetStats().fp ? 0 : 1;
	 
	if (!m_reject.effect[id].empty())
	{
		Vec3 front(m_pWeapon->GetEntity()->GetWorldTM().TransformVector(FORWARD_DIRECTION));
		Vec3 up(m_pWeapon->GetEntity()->GetWorldTM().TransformVector(Vec3(0.0f,0.0f,1.0f)));

		CActor *pActor = m_pWeapon->GetOwnerActor();
		IMovementController * pMC = pActor ? pActor->GetMovementController() : 0;
		if (pMC)
		{
			SMovementState info;
			pMC->GetMovementState(info);

			front = info.aimDirection;
			up = info.upDirection;
		}

		Vec3 dir = front.Cross(up);
		dir+=(up*0.65f);
		dir.normalize();

		m_pWeapon->SpawnEffect(slot, m_reject.effect[id].c_str(), m_reject.helper[id].c_str(),
			Vec3(0,0,0), dir, m_reject.scale[id]);
	}
}

//------------------------------------------------------------------------
int CSingle::GetDamage() const
{
	return m_fireparams.damage;
}

//------------------------------------------------------------------------
float CSingle::GetRecoil() const
{
	return m_recoil;
}

//------------------------------------------------------------------------
float CSingle::GetSpread() const
{
	return m_spread;
}

//------------------------------------------------------------------------
const char *CSingle::GetCrosshair() const
{
	return m_fireparams.crosshair.c_str();
}

//------------------------------------------------------------------------
void CSingle::UpdateRecoil(float frameTime)
{
	// spread
	float spread_add = 0.0f;
	float spread_sub = 0.0f;

	if (m_spreadparams.decay>0)
	{
		// shot
		if (m_fired)
			spread_add = m_spreadparams.attack;

		spread_sub = frameTime*(m_spreadparams.max-m_spreadparams.min)/m_spreadparams.decay;

		m_spread += spread_add-spread_sub;
		m_spread = CLAMP(m_spread, m_spreadparams.min, m_spreadparams.max);
	}
	else
		m_spread = m_spreadparams.min;

	// recoil
	float recoil_add = 0.0f;
	float recoil_sub = 0.0f;

	if (m_recoilparams.decay>0.0f)
	{
		if (m_fired)
			recoil_add = m_recoilparams.attack;

		recoil_sub = frameTime*m_recoilparams.max_recoil/m_recoilparams.decay;
		m_recoil += recoil_add-recoil_sub;
		m_recoil = CLAMP(m_recoil, 0.0f, m_recoilparams.max_recoil);
	}
	else
		m_recoil = 0.0f;
	
	if ((m_recoilparams.max.x>0) || (m_recoilparams.max.y>0))
	{
		Vec2 recoil_dir_add(0.0f, 0.0f);

		if (m_fired)
		{
			int n = m_recoilparams.hints.size();

			if (m_recoil_dir_idx >= 0 && m_recoil_dir_idx<n)
			{
				recoil_dir_add = m_recoilparams.hints[m_recoil_dir_idx];

				if (m_recoil_dir_idx+1>=n)
					m_recoil_dir_idx = 0;
				else
					++m_recoil_dir_idx;
			}
		}

		CActor *pOwner = m_pWeapon->GetOwnerActor();
		
		if (pOwner && m_pWeapon->IsCurrentItem())
		{
			if (m_fired)
			{
				Vec2 rdir(Random()*m_recoilparams.randomness,BiRandom(m_recoilparams.randomness));
				m_recoil_dir = Vec2(recoil_dir_add.x+rdir.x, recoil_dir_add.y+rdir.y);
				m_recoil_dir.normalize();
			}

			if (m_recoil > 0.001f)
			{
				float t = m_recoil/m_recoilparams.max_recoil;
				Vec2 new_offset = Vec2(m_recoil_dir.x*m_recoilparams.max.x, m_recoil_dir.y*m_recoilparams.max.y)*t*3.141592f/180.0f;
				m_recoil_offset = new_offset*0.66f+m_recoil_offset*0.33f;
				pOwner->SetViewAngleOffset(Vec3(m_recoil_offset.x, 0.0f, m_recoil_offset.y));
			}
			else
				ResetRecoil(false);
		}
	}
	else
		ResetRecoil();
}

//------------------------------------------------------------------------
void CSingle::ResetRecoil(bool spread)
{
	m_recoil = 0.0f;
	m_recoil_dir_idx = 0;
	m_recoil_dir = Vec2(0.0f,0.0f);
	m_recoil_offset = Vec2(0.0f,0.0f);
	if (spread)
		m_spread = m_spreadparams.min;

	CActor *pOwner = m_pWeapon->GetOwnerActor();
	if (pOwner && m_pWeapon->IsCurrentItem())
		pOwner->SetViewAngleOffset(Vec3(0.0f,0.0f,0.0f));
}

//------------------------------------------------------------------------
void CSingle::NetShoot(const Vec3 &hit)
{
	const char *ammo = m_fireparams.ammo_type.c_str();
	const char *action = m_actions.fire_cock.c_str();

	int ammoCount=2; // FIX
	if (ammoCount == 1)
		action = m_actions.fire.c_str();

	m_pWeapon->ResetAnimation();
	m_pWeapon->PlayAction(action, 0, false, CItem::eIPAF_Default|CItem::eIPAF_NoBlend);

	Vec3 pos = GetFiringPos(hit);
	Vec3 dir = ApplySpread(GetFiringDir(hit));
	Vec3 vel = GetFiringVelocity();

	CProjectile *pAmmo = m_pWeapon->SpawnAmmo(ammo);
	if (pAmmo)
	{
		pAmmo->SetParams(m_shooterId, m_pWeapon->GetEntityId(), m_fireparams.damage);
		pAmmo->SetDestination(m_pWeapon->GetDestination());
		pAmmo->SetRemote(true);
		pAmmo->Launch(pos, dir, vel);

		if ((!m_tracerparams.geometry.empty() || !m_tracerparams.effect.empty()) && (!m_ammoid || (m_ammoid%m_tracerparams.frequency==0)))
		{
			CTracerPath path;
			path.AddPoint(hit);

			CTracerManager::STracerParams params;
			params.geometry = m_tracerparams.geometry.c_str();
			params.effect = m_tracerparams.effect.c_str();
			params.path = &path;
			params.position = pos;
			params.scale = m_tracerparams.scale;
			params.speed = m_tracerparams.speed;
			params.lifetime = m_tracerparams.lifetime;

			path.First();
			g_pGame->GetWeaponSystem()->GetTracerManager().EmitTracer(params);
		}

		m_projectileId = pAmmo->GetEntity()->GetId();
		++m_ammoid;
	}

	m_pWeapon->OnShoot(m_shooterId, pAmmo?pAmmo->GetEntity()->GetId():0, ammo, pos, dir, vel);

	MuzzleFlashEffect(true);
	RejectEffect();

	m_fired = true;
	m_next_shot = 0.0f;

	return;
}


//------------------------------------------------------------------------
void CSingle::CacheTracer()
{
	if (!m_tracerparams.geometry.empty())
	{
		IStatObj *pStatObj = GetISystem()->GetI3DEngine()->LoadStatObj(m_tracerparams.geometry.c_str());
		if (pStatObj)
		{
			pStatObj->AddRef();
			m_tracerCache.push_back(pStatObj);
		}
	}

	if (!m_ooatracerparams.geometry.empty())
	{
		IStatObj *pStatObj = GetISystem()->GetI3DEngine()->LoadStatObj(m_ooatracerparams.geometry.c_str());
		if (pStatObj)
		{
			pStatObj->AddRef();
			m_tracerCache.push_back(pStatObj);
		}
	}
}

//------------------------------------------------------------------------
void CSingle::ClearTracerCache()
{
	for (std::vector<IStatObj *>::iterator it=m_tracerCache.begin(); it!=m_tracerCache.end(); ++it)
		(*it)->Release();

	m_tracerCache.resize(0);
}
