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

-------------------------------------------------------------------------
History:
- 19:12:2005   12:10 : Created by Márcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "Beam.h"
#include "Weapon.h"
#include "Actor.h"
#include "Game.h"
#include "GameRules.h"
#include <ISound.h>
#include <IEntitySystem.h>


//------------------------------------------------------------------------
CBeam::CBeam()
:	m_effectId(0),
	m_fireLoopId(INVALID_SOUNDID),
	m_hitSoundId(INVALID_SOUNDID)
{
}

//------------------------------------------------------------------------
CBeam::~CBeam()
{
}

//std::vector<Vec3> gpoints;

//------------------------------------------------------------------------
void CBeam::Update(float frameTime, uint frameId)
{

	//for (std::vector<Vec3>::iterator it=gpoints.begin(); gpoints.end()!=it;++it)
		//GetISystem()->GetIRenderer()->GetIRenderAuxGeom()->DrawSphere(*it, 0.35f, ColorB(255, 128, 0, 255));

	CSingle::Update(frameTime, frameId);

	if (m_firing)
	{
		if (m_spinUpTimer>0.0f)
		{
			m_spinUpTimer -= frameTime;
			if (m_spinUpTimer>0.0f)
				return;

			m_spinUpTimer = 0.0f;

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

			m_pWeapon->PlayAction(m_beamactions.blast.c_str());
			m_fireLoopId = m_pWeapon->PlayAction(m_actions.fire.c_str(), 0, true);

			ISound *pSound = m_pWeapon->GetSoundProxy()->GetSound(m_fireLoopId);
			if (pSound)
				pSound->SetLoopMode(true);

			SpinUpEffect(false);

			m_effectId = m_pWeapon->AttachEffect(slot, 0, true, m_effectparams.effect[id].c_str(), 
				m_effectparams.helper[id].c_str());
		}

		bool hitValid = false;
		ray_hit rayhit;

		Vec3 hit = GetProbableHit(m_beamparams.range, &hitValid, &rayhit);
		Vec3 pos = GetFiringPos(hit);
		Vec3 dir = GetFiringDir(hit);

		if (m_effectId)
		{
			Vec3 epos(m_pWeapon->GetEffectWorldTM(m_effectId).GetTranslation());
			Matrix34 etm(Matrix33::CreateRotationVDir(dir));
			etm.AddTranslation(epos);

			m_pWeapon->SetEffectWorldTM(m_effectId, etm);
			
			IParticleEmitter *pEmitter = m_pWeapon->GetEffectEmitter(m_effectId);
			if (pEmitter)
			{
				ParticleTarget targetOptions;
	
				targetOptions.bTarget = true;
				targetOptions.bExtendCount = true;
				targetOptions.bExtendLife = false;
				targetOptions.bExtendSpeed = true;
				targetOptions.bPriority = true;
				targetOptions.vTarget = hit;
	
				pEmitter->SetTarget(targetOptions);
			}
		}

		if (hitValid)
		{
			if (!m_lastHitValid)
			{
				m_hitSoundId = m_pWeapon->PlayAction(m_beamactions.hit.c_str(), 0, true, CItem::eIPAF_Default|CItem::eIPAF_ForceThirdPerson|CItem::eIPAF_SoundLooped|CItem::eIPAF_SoundStartPaused);
				ISound *pSound = m_pWeapon->GetISound(m_hitSoundId);
				if (pSound)
				{
					pSound->SetPosition(hit);
					pSound->SetPaused(false);
				}
			}

			// update sound pos/param
			if (m_hitSoundId != INVALID_SOUNDID)
			{
				ISound *pSound = m_pWeapon->GetISound(m_hitSoundId);
				if (pSound)
				{
					pSound->SetParam("angle", RAD2DEG(acosf(rayhit.n.Dot(dir))));
					pSound->SetPosition(hit);
					pSound->SetLinePoints(hit, pos);
				}
			}

			Hit(rayhit, dir);

			if (!m_beamparams.hit_decal.empty())
			{
				//if (!m_lastHitValid)
				if (!rayhit.pCollider || !GetISystem()->GetIEntitySystem()->GetEntityFromPhysics(rayhit.pCollider))
					Decal(rayhit, dir);
				//else
				//{
					//Decal(rayhit, dir);
					//DecalLine(m_lastOrg, pos, m_lastHit, rayhit.pt, m_beamparams.hit_decal_size*0.1f);
				//}
			}

			if (!m_beamparams.hit_effect.empty())
			{
				IParticleEffect *pParticleEffect = GetISystem()->GetI3DEngine()->FindParticleEffect(m_beamparams.hit_effect.c_str());
				if (pParticleEffect)
					pParticleEffect->Spawn(true, IParticleEffect::ParticleLoc(rayhit.pt, rayhit.n, m_beamparams.hit_effect_scale));
			}

			if (m_tickTimer>0.0f)
			{
				m_tickTimer -= frameTime;
				if (m_tickTimer <= 0.0f)
				{
					Tick(rayhit, dir);

					m_tickTimer = m_beamparams.tick;

					TickDamage(rayhit, dir);
				}
			}

			m_lastOrg = pos;
			m_lastHit = rayhit.pt;
			m_lastHitValid = true;
		}
		else
			m_lastHitValid = false;
	}
	else
		m_lastHitValid = false;

	if (!m_lastHitValid)
	{
		if (m_hitSoundId != INVALID_SOUNDID)
		{
			m_pWeapon->StopSound(m_hitSoundId);
			m_hitSoundId = INVALID_SOUNDID;
		}
	}
}

//------------------------------------------------------------------------
void CBeam::ResetParams(const struct IItemParamsNode *params)
{
	CSingle::ResetParams(params);

	const IItemParamsNode *beam = params?params->GetChild("beam"):0;
	const IItemParamsNode *effect = params?params->GetChild("effect"):0;
	const IItemParamsNode *actions = params?params->GetChild("actions"):0;
	m_beamparams.Reset(beam);
	m_effectparams.Reset(effect);
	m_beamactions.Reset(actions);
}

//------------------------------------------------------------------------
void CBeam::PatchParams(const struct IItemParamsNode *patch)
{
	CSingle::PatchParams(patch);
	
	const IItemParamsNode *beam = patch?patch->GetChild("beam"):0;
	const IItemParamsNode *effect = patch?patch->GetChild("effect"):0;
	const IItemParamsNode *actions = patch?patch->GetChild("actions"):0;
	m_beamparams.Reset(beam, false);
	m_effectparams.Reset(effect, false);
	m_beamactions.Reset(actions, false);
}

//------------------------------------------------------------------------
void CBeam::Activate(bool activate)
{
	//gpoints.resize(0);
	CSingle::Activate(activate);

	m_firing = false;

	if (m_fireLoopId != INVALID_SOUNDID)
	{
		m_pWeapon->StopSound(m_fireLoopId);
		m_fireLoopId = INVALID_SOUNDID;
	}

	if (m_hitSoundId != INVALID_SOUNDID)
	{
		m_pWeapon->StopSound(m_hitSoundId);
		m_hitSoundId = INVALID_SOUNDID;
	}

	SpinUpEffect(false);

	m_lastHitValid=false;
	m_tickTimer = m_beamparams.tick;
}

//------------------------------------------------------------------------
bool CBeam::OutOfAmmo() const
{
	return false;
}

//------------------------------------------------------------------------
bool CBeam::CanReload() const
{
	return false;
}

//------------------------------------------------------------------------
bool CBeam::CanFire(bool considerAmmo) const
{
	return !m_pWeapon->IsBusy();
}

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

	m_lastHitValid = false;
	m_firing = true;
	m_spinUpTimer = m_fireparams.spin_up_time;
	m_tickTimer = m_beamparams.tick;

	SpinUpEffect(true);
	m_pWeapon->PlayAction(m_actions.spin_up.c_str());
	m_pWeapon->RequireUpdate(true);
	m_pWeapon->EnableUpdate(true);
}

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

	SpinUpEffect(false);

	if (m_effectId)
	{
		m_pWeapon->AttachEffect(CItem::eIGS_FirstPerson, m_effectId, false);
		m_effectId=0;
	}

	if (m_fireLoopId != INVALID_SOUNDID)
	{
		m_pWeapon->StopSound(m_fireLoopId);
		m_fireLoopId = INVALID_SOUNDID;
	}

	m_pWeapon->PlayAction(m_actions.spin_down.c_str());
	m_pWeapon->RequireUpdate(false);
	m_pWeapon->EnableUpdate(false);
}

//------------------------------------------------------------------------
void CBeam::DecalLine(const Vec3 &org0, const Vec3 &org1, const Vec3 &hit0, const Vec3 &hit1, float step)
{
	Vec3 hitStepDir = (hit1-hit0);
	float dist = hitStepDir.len();
	if (dist < 2.0f*step)	// don't create line if too close
		return;

	hitStepDir /= dist;
	int steps = (int)((dist-(2.0f*step))/step);
	if (!steps)
		return;

	Vec3 orgStepDir = (org1-org0);
	float orgDist = orgStepDir.len();
	float orgStep = 0.0f;

	if (orgDist>0.05f)
	{
		orgStep = orgDist/(float)steps;
		orgStepDir /= orgDist;
	}
	else
		orgStepDir=Vec3(0,0,0);
	
	float hitCurrStep = step;
	float orgCurrStep = orgStep;

	Vec3 org=org0;
	Vec3 hit=hit0;

//	CryLogAlways("line decals: %d (dist: %.3f)", steps, dist);

	while(steps--)
	{
		hit=hit0+hitStepDir*hitCurrStep;
		org=org0+orgStepDir*orgCurrStep;

		Vec3 dir = (hit-org).normalized()*m_beamparams.range;

		ray_hit rayhit;
		IPhysicalEntity *pSkipEntity = m_pWeapon->GetOwnerActor()->GetEntity()->GetPhysics();

		if (GetISystem()->GetIPhysicalWorld()->RayWorldIntersection(org, dir, ent_all,
			rwi_stop_at_pierceable|rwi_colltype_any|rwi_any_hit, &rayhit, 1, pSkipEntity))
		{
			Decal(rayhit, dir);
			//CryLogAlways("decal: %.3f, %.3f, %.3f", rayhit.pt.x, rayhit.pt.y, rayhit.pt.z);
		}

		hitCurrStep += step;
		orgCurrStep += orgStep;
	}
}


//------------------------------------------------------------------------
void CBeam::Decal(const ray_hit &rayhit, const Vec3 &dir)
{
	CryEngineDecalInfo decal;

	//	gpoints.push_back(rayhit.pt);
	decal.vPos = rayhit.pt;
	decal.vNormal = rayhit.n;
	decal.fSize = m_beamparams.hit_decal_size;
	decal.fLifeTime = m_beamparams.hit_decal_lifetime;
	decal.bAdjustPos = true;

	strcpy(decal.szTextureName, m_beamparams.hit_decal.c_str());

	decal.fAngle = RAD2DEG(acosf(rayhit.n.Dot(dir)));
	decal.vHitDirection = dir;

	if (false && rayhit.pCollider)
	{
		IEntity *pEntity = GetISystem()->GetIEntitySystem()->GetEntityFromPhysics(rayhit.pCollider);
		if (pEntity)
		{
			IEntityRenderProxy *pRenderProxy = (IEntityRenderProxy*)pEntity->GetProxy(ENTITY_PROXY_RENDER);;
			if (pRenderProxy)
				decal.pDecalOwner = pRenderProxy->GetRenderNode();
		}

		decal.nPartID = rayhit.partid;
	}

	GetISystem()->GetI3DEngine()->CreateDecal(decal);
}

//------------------------------------------------------------------------
void CBeam::Hit(ray_hit &hit, const Vec3 &dir)
{
}

//------------------------------------------------------------------------
void CBeam::Tick(ray_hit &hit, const Vec3 &dir)
{
}

//------------------------------------------------------------------------
void CBeam::TickDamage(ray_hit &hit, const Vec3 &dir)
{
	IEntitySystem *pEntitySystem = GetISystem()->GetIEntitySystem();
	IGameFramework *pGameFramework = GetISystem()->GetIGame()->GetIGameFramework();
	IEntity *pEntity = pEntitySystem->GetEntityFromPhysics(hit.pCollider);

	if (pEntity)
	{
		CGameRules *pGameRules = g_pGame->GetGameRules();

		CGameRules::HitInfo info(m_pWeapon->GetOwnerId(), pEntity->GetId(), m_pWeapon->GetEntityId(), m_fireparams.damage, 0.25f,
			pGameRules->GetHitMaterialIdFromSurfaceId(hit.surface_idx), hit.partid, pGameRules->GetHitTypeId("frost"),
			hit.pt, dir, hit.n);

		pGameRules->ServerHit(info);
	}
}