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

-------------------------------------------------------------------------
History:
- 18:10:2005   14:14 : Created by Márcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "Game.h"
#include "Projectile.h"
#include "WeaponSystem.h"
#include "ISerialize.h"
#include "IGameObject.h"

#include <IEntitySystem.h>
#include <ISound.h>
#include <IItemSystem.h>
#include <IAgent.h>
#include "ItemParamReader.h"
#include "GameRules.h"
#include "IMaterialEffects.h"


//------------------------------------------------------------------------
CProjectile::CProjectile()
: m_pEntity(0),
	m_whizSoundId(INVALID_SOUNDID),
	m_trailSoundId(INVALID_SOUNDID),
	m_trailEffectId(-1),
	m_sticky(false),
	m_stuck(0),
	m_stickyCollisions(0),
	m_pStickyBuddy(0),
	m_params(0),
	m_tracerpath(0),
	m_destroying(false),
	m_remote(false)
{
}

//------------------------------------------------------------------------
CProjectile::~CProjectile()
{
	m_params->Release();
	m_pGameObject->EnablePhysicsEvent(false, eEPE_OnCollisionLogged);
	if (g_pGame)
		g_pGame->GetWeaponSystem()->RemoveProjectile(this);
}

//------------------------------------------------------------------------
bool CProjectile::Init(IGameObject *pGameObject)
{
	m_pGameObject = pGameObject;
	m_pGameObject->EnablePhysicsEvent(true, eEPE_OnCollisionLogged);

	m_pEntity = pGameObject->GetEntity();
	g_pGame->GetWeaponSystem()->AddProjectile(m_pEntity, this);

	return true;
}

//------------------------------------------------------------------------
void CProjectile::PostInit(IGameObject *pGameObject)
{
	m_pGameObject->EnableUpdateSlot(this, 0);

	m_params = g_pGame->GetWeaponSystem()->GetAmmoParams(m_pEntity->GetClass()->GetName());
	m_params->AddRef();
}

//------------------------------------------------------------------------
void CProjectile::Release()
{
	delete this;
}

//------------------------------------------------------------------------
void CProjectile::Serialize(TSerialize ser, unsigned aspects)
{
	if(ser.GetSerializationTarget() != eST_Network)
	{
		ser.Value("Remote", m_remote);
		ser.Value("Sticky", m_sticky);
		// m_tracerpath should be serialized but the template-template stuff doesn't work under VS2005
		ser.Value("Owner", m_ownerId, NSerPolicy::AC_EntityId());
		ser.Value("Weapon", m_weaponId, NSerPolicy::AC_EntityId());
		ser.Value("TrailEffect", m_trailEffectId);
		ser.Value("TrailSound", m_trailSoundId);
		ser.Value("WhizSound", m_whizSoundId);
		ser.Value("Damage", m_damage);
		ser.Value("Destroying", m_destroying);
		ser.Value("LastPos", m_last);
		ser.Value("Mass", m_mass);
	}
}

//------------------------------------------------------------------------
void CProjectile::Update(SEntityUpdateContext &ctx, int updateSlot)
{
	Vec3 pos = m_pEntity->GetWorldPos();

	WaterCollision(pos, m_last);

	if (m_tracerpath)
		m_tracerpath->AddPoint(pos);

	// update whiz
	if (m_whizSoundId == INVALID_SOUNDID)
	{
		IActor *pActor = g_pGame->GetIGameFramework()->GetClientActor();
		if (pActor && (m_ownerId != pActor->GetEntityId()))
		{
			float probability = 0.65f;

			if (Random()<=probability)
			{

				Lineseg line(m_last, pos);
				Vec3 player = pActor->GetEntity()->GetWorldPos();

				float t;
				float distanceSq=Distance::Point_LinesegSq(player, line, t);

				if (distanceSq < 4*4 && (t>=0.0f && t<=1.0f))
				{
					if (distanceSq > 0.75*0.75)
						WhizSound(true, line.start+((line.end-line.start)*t));
				}
			}
		}
	}

	m_last = pos;
}

//------------------------------------------------------------------------
void CProjectile::HandleEvent(const SGameObjectEvent &event)
{
	if (m_destroying)
		return;

	if (!strcmpi(event.event, "oncollision"))
	{
		EventPhysCollision *pCollision = (EventPhysCollision *)event.ptr;

		const IItemParamsNode *collision = m_params->GetChild("collision");
		if (collision)
		{
			const char *effect=0;
			const char *sound=0;
			float	scale=1.0f;
			CItemParamReader reader(collision);
			reader.Read("effect", effect);
			reader.Read("sound", sound);
			reader.Read("scale", scale);

			if (effect && effect[0])
			{
				IParticleEffect *pParticleEffect = GetISystem()->GetI3DEngine()->FindParticleEffect(effect);
				if (pParticleEffect)
					pParticleEffect->Spawn(true, IParticleEffect::ParticleLoc(pCollision->pt, pCollision->n, scale));
			}

			if (sound)
			{
				_smart_ptr<ISound> pSound = GetISystem()->GetISoundSystem()->CreateSound(sound, FLAG_SOUND_DEFAULT_3D);
				pSound->SetPosition(pCollision->pt);
				pSound->Play();
			}

			if (m_tracerpath)
				m_tracerpath->AddPoint(pCollision->pt);
		}

		if (m_sticky && m_stuck < MAX_STICKY_POINTS)
			Stick(pCollision);
	}
}

//------------------------------------------------------------------------
void CProjectile::ProcessEvent(SEntityEvent &event)
{
	switch(event.event)
	{
	case ENTITY_EVENT_TIMER:
		{
			switch(event.nParam[0])
			{
			case ePTIMER_SHOWTIME:
				{
					m_pEntity->SetSlotFlags(0, m_pEntity->GetSlotFlags(0)|ENTITY_SLOT_RENDER);
				}
				break;
			case ePTIMER_LIFETIME:
				{
					if (g_pGame->GetIGameFramework()->IsServer()||(GetEntity()->GetFlags()&(ENTITY_FLAG_CLIENT_ONLY|ENTITY_FLAG_SERVER_ONLY)))
						Explode(true);
				}
				break;
			case ePTIMER_STICKY:
				{
					if (m_stuck<MIN_STICKY_POINTS)
						Unstick();
				}
				break;
			}
		}
		break;
	}
}

//------------------------------------------------------------------------
IEntity *CProjectile::GetEntity()
{
	return m_pEntity;
}

//------------------------------------------------------------------------
void CProjectile::SetAuthority(bool auth)
{
}

//------------------------------------------------------------------------
void CProjectile::LoadGeometry()
{
	const IItemParamsNode *geometry = m_params->GetChild("geometry");
	if (geometry)
	{
		const IItemParamsNode *name = geometry->GetChild("firstperson");
		if (name)
		{
			const char *modelName = name->GetAttribute("name");

			if (modelName && modelName[0])
			{
				Vec3 angles(0,0,0);
				Vec3 position(0,0,0);
				float scale=1.0f;
				name->GetAttribute("position", position);
				name->GetAttribute("angles", angles);
				name->GetAttribute("scale", scale);

				Matrix34 tm = Matrix34(Matrix33::CreateRotationXYZ(DEG2RAD(angles)));
				tm.Scale(Vec3(scale, scale, scale));
				tm.SetTranslation(position);

				m_pEntity->LoadGeometry(0, modelName);
				m_pEntity->SetSlotLocalTM(0, tm);
			}
		}
	}
}

//------------------------------------------------------------------------
void CProjectile::Physicalize(const Vec3 &pos, const Vec3 &dir, const Vec3 &velocity)
{
	const IItemParamsNode *physics = m_params->GetChild("physics");
	if (!physics)
		return;

	int entType = PE_PARTICLE;
	const char *typ=physics->GetAttribute("type");
	if (typ)
	{
		if (!strcmpi(typ, "particle"))
			entType=PE_PARTICLE;
		else if (!strcmpi(typ, "rigid"))
			entType=PE_RIGID;
		else
		{
			GameWarning("Unknow physicalization type '%s' for projectile '%s'!", typ, m_pEntity->GetClass()->GetName());
		}
	}

	CItemParamReader reader(physics);
	float speed=0.0f;
	reader.Read("speed", speed);
	Vec3 totalVelocity = (dir*speed)+velocity;

	float mass=1.0f;
	reader.Read("mass", mass);
	m_mass = mass;

	int max_logged_collisions=1;
 	reader.Read("max_logged_collisions", max_logged_collisions);

	Vec3 spin(0,0,0);
	reader.Read("spin", spin);
	Vec3 spin_random(0,0,0);
	reader.Read("spin_random", spin_random);
	spin_random.Set(BiRandom(spin_random.x), BiRandom(spin_random.y), BiRandom(spin_random.z));
	spin += spin_random;
	spin = DEG2RAD(spin);

	m_sticky = false;
	reader.Read("sticky", m_sticky);

	if (entType==PE_PARTICLE)
	{
		pe_params_particle pparams;
		reader.Read("thickness", pparams.thickness);
		reader.Read("air_resistance", pparams.kAirResistance);
		reader.Read("water_resistance", pparams.kWaterResistance);
		reader.Read("gravity", pparams.gravity);
		reader.Read("water_gravity", pparams.waterGravity);
		reader.Read("thrust", pparams.accThrust);
		reader.Read("lift", pparams.accLift);
		reader.Read("min_bounce_speed", pparams.minBounceVel);
		reader.Read("pierceability", pparams.iPierceability);
		
		const char *material=0;
		reader.Read("material", material);

		if (material)
		{
			ISurfaceType *pSurfaceType = GetISystem()->GetI3DEngine()->GetMaterialManager()->GetSurfaceTypeByName(material);
			if (pSurfaceType)
				pparams.surface_idx = pSurfaceType->GetId();
		}
	
		int flag=0;
		reader.Read("single_contact", flag);
		pparams.flags = flag?particle_single_contact:0; flag=0;
		reader.Read("no_roll", flag);
		pparams.flags |= flag?particle_no_roll:0; flag=0;
		reader.Read("no_spin", flag);
		pparams.flags |= flag?particle_no_spin:0; flag=0;
		reader.Read("no_path_alignment", flag);
		pparams.flags |= flag?particle_no_path_alignment:0; flag=0;
		pparams.wspin = spin;

		pparams.heading = totalVelocity.GetNormalized();
		pparams.velocity = totalVelocity.GetLength();

		if (m_ownerId)
		{
			IEntity *pEntity = GetISystem()->GetIEntitySystem()->GetEntity(m_ownerId);
			if (pEntity)
				pparams.pColliderToIgnore = pEntity->GetPhysics();
		}

		pparams.mass= mass;

		SEntityPhysicalizeParams params;
		params.type = PE_PARTICLE;
		params.mass = mass;
		params.pParticle = &pparams;

		m_pEntity->Physicalize(params);
	}
	else
	{
		SEntityPhysicalizeParams params;
		params.type = PE_RIGID;
		params.mass = mass;
		params.nSlot = 0;

		m_pEntity->Physicalize(params);

		pe_action_set_velocity velocity;
		m_pPhysicalEntity = m_pEntity->GetPhysics();
		velocity.v = totalVelocity;
		velocity.w = spin;
		m_pPhysicalEntity->Action(&velocity);

		const char *material=0;
		reader.Read("material", material);

		if (material)
		{
			int sfid=0;
			ISurfaceType *pSurfaceType = GetISystem()->GetI3DEngine()->GetMaterialManager()->GetSurfaceTypeByName(material);
			if (pSurfaceType)
			{
				sfid = pSurfaceType->GetId();

				pe_params_part part;
				part.ipart = 0;

				m_pEntity->GetPhysics()->GetParams(&part);
				for (int i=0; i<part.nMats; i++)
					part.pMatMapping[i] = sfid;
			}
		}
	}

	m_pPhysicalEntity = m_pEntity->GetPhysics();

	pe_simulation_params simulation;
	simulation.maxLoggedCollisions = max_logged_collisions;

	pe_params_flags flags;
	flags.flagsOR = pef_log_collisions;

	m_pPhysicalEntity->SetParams(&simulation);
	m_pPhysicalEntity->SetParams(&flags);
}

//------------------------------------------------------------------------
void CProjectile::SetTracerPath(CTracerPath *path)
{
	m_tracerpath=path;
}

//------------------------------------------------------------------------
void CProjectile::SetParams(EntityId ownerId, EntityId weaponId, int damage)
{
	m_ownerId = ownerId;
	m_weaponId = weaponId;
	m_damage = damage;
}

//------------------------------------------------------------------------
void CProjectile::Launch(const Vec3 &pos, const Vec3 &dir, const Vec3 &velocity)
{
	LoadGeometry();
	Physicalize(pos, dir, velocity);

	IEntityRenderProxy *pProxy = static_cast<IEntityRenderProxy *>(m_pEntity->GetProxy(ENTITY_PROXY_RENDER));
	if (pProxy && pProxy->GetRenderNode())
		pProxy->GetRenderNode()->SetViewDistRatio(255);

	Matrix34 worldTM=Matrix34(Matrix33::CreateRotationVDir(dir.GetNormalizedSafe()));
	worldTM.SetTranslation(pos);
	m_pEntity->SetWorldTM(worldTM);

	TrailSound(true);
	TrailEffect(true);

	float lifetime = 0.0f;
	lifetime = GetParam("lifetime", lifetime);
	if (lifetime > 0.0f)
		m_pEntity->SetTimer(ePTIMER_LIFETIME, (int)(lifetime*1000.0f));

	float showtime = 0.0f;
	showtime = GetParam("showtime", showtime);
	if (showtime > 0.0f)
	{
		m_pEntity->SetSlotFlags(0, m_pEntity->GetSlotFlags(0)&(~ENTITY_SLOT_RENDER));
		m_pEntity->SetTimer(ePTIMER_SHOWTIME, (int)(showtime*1000.0f));
	}
	else
		m_pEntity->SetSlotFlags(0, m_pEntity->GetSlotFlags(0)|ENTITY_SLOT_RENDER);

	// register with ai if needed
	const char *typeName=0;
	typeName=GetParam("aitype", typeName);

	if (typeName && typeName[0])
	{
		ushort aiType=0;
		if (!stricmp(typeName, "grenade"))
			aiType=AIOBJECT_GRENADE;
		else if (!stricmp(typeName, "rpg"))
			aiType=AIOBJECT_RPG;
		else
			aiType=AIOBJECT_NONE;

		AIObjectParameters params;
		m_pEntity->RegisterInAISystem(aiType, params);
	}

	m_last = pos;
}

//------------------------------------------------------------------------
void CProjectile::Destroy()
{
	m_destroying=true;
	GetISystem()->GetIEntitySystem()->RemoveEntity(m_pEntity->GetId());
}

//------------------------------------------------------------------------
bool CProjectile::WaterCollision(const Vec3 &current, const Vec3 &last)
{
	float waterLevel = GetISystem()->GetI3DEngine()->GetWaterLevel(&current);
	bool currentInWater = waterLevel>current.z;
	bool lastInWater = waterLevel>last.z;

	Vec3 delta = current-last;

	if (delta.GetLengthSquared()<0.01f)
		return currentInWater;

	Vec3 dir = delta.GetNormalized();

	if (currentInWater != lastInWater)
	{
		float vd = dir.Dot(Vec3(0,0,1));
		float v0 = -(last.Dot(Vec3(0,0,1))-waterLevel);
		float t = v0/vd;

		Vec3 splash = last+(dir*t);

		// call gamerules here
	}

	return currentInWater;
}

//------------------------------------------------------------------------
bool CProjectile::IsRemote() const
{
	return m_remote;
}

//------------------------------------------------------------------------
void CProjectile::SetRemote(bool remote)
{
	m_remote = remote;
}

//------------------------------------------------------------------------
void CProjectile::Explode(bool destroy)
{
	const IItemParamsNode *params = m_params->GetChild("explosion");
	if (params)
	{
		float		max_radius	= 5.0f;
		float		pressure		= 200.0f;
		float		damage			= 0.0f;
		float		hole_size		= 0.0f;
		float		terrain_hole_size = 3.0f;
		string	decal				= "";
		string	effect			= "";
		float		effect_scale= 1;
		
		CItemParamReader reader(params);
		reader.Read("max_radius", max_radius);
		reader.Read("pressure", pressure);
		reader.Read("hole_size", hole_size);
		reader.Read("terrain_hole_size", terrain_hole_size);
		reader.Read("decal", decal);
		reader.Read("effect", effect);
		reader.Read("effect_scale", effect_scale);

		pe_status_dynamics dyn;
		m_pPhysicalEntity->GetStatus(&dyn);

		Vec3 dir(0,0,1);
		if (!dyn.v.IsZero())
			dir = dyn.v.normalized();
		Vec3 pos = GetEntity()->GetWorldPos();

		CGameRules *pGameRules = g_pGame->GetGameRules();
		CGameRules::ExplosionInfo explosionInfo(m_ownerId, m_weaponId, m_damage, pos, dir, max_radius, pressure, hole_size);
		explosionInfo.effect = effect;
		explosionInfo.effectScale = effect_scale;

		pe_params_buoyancy bu;
		Vec3 grav;
		GetISystem()->GetIPhysicalWorld()->CheckAreas(explosionInfo.pos, grav, &bu);
		// 0 for water, 1 for air
		bool inWater = false;
		if (bu.waterPlane.origin.z > explosionInfo.pos.z)
			inWater = true;
		bool zeroG = false;
		if (grav.GetLength() < 0.0001f)
			zeroG = true;

		SMFXRunTimeEffectParams params;
		params.pos = explosionInfo.pos;
		params.normal = explosionInfo.dir;
		params.inWater = inWater;
		params.inZeroG = zeroG;


		static const unsigned int objTypes = ent_all;    
		static const int flags = rwi_stop_at_pierceable|rwi_colltype_any|rwi_any_hit;
		IItem *pItem = GetISystem()->GetIGame()->GetIGameFramework()->GetIItemSystem()->GetItem(explosionInfo.weaponId);
		IPhysicalEntity *physEnt = NULL;
		string wepClass = GetEntity()->GetClass()->GetName();
		if (wepClass.empty())
			wepClass = "generic";
		ray_hit ray;
		int mat = 0;
		int col = GetISystem()->GetIPhysicalWorld()->RayWorldIntersection(explosionInfo.pos, grav, objTypes, flags, &ray, 1, physEnt);
		if (col)
		{
			mat = ray.surface_idx;
		}
		string query = wepClass + "_explode";
		const char *meffect = GetISystem()->GetIGame()->GetIGameFramework()->GetIMaterialEffects()->GetEffectString(query.c_str(), mat);
		int defaultMat = GetISystem()->GetIGame()->GetIGameFramework()->GetIMaterialEffects()->m_defaultMatID;
		if (!meffect)
			meffect = GetISystem()->GetIGame()->GetIGameFramework()->GetIMaterialEffects()->GetEffectString(query.c_str(), defaultMat);

		if (meffect)
			GetISystem()->GetIGame()->GetIGameFramework()->GetIMaterialEffects()->ExecuteEffect(meffect, params);

		pGameRules->ServerExplosion(explosionInfo);
	}

	if (destroy)
		Destroy();
}

//------------------------------------------------------------------------
void CProjectile::TrailSound(bool enable, const Vec3 &dir)
{
	if (enable)
	{
		const IItemParamsNode *trail=m_params->GetChild("trail");
		if (!trail)
			return;

		const char *sound=0;
		CItemParamReader reader(trail);
		reader.Read("sound", sound);
		if (sound && sound[0])
		{
			float radius=5.0f; reader.Read("sound_radius", radius);

			m_trailSoundId = GetSoundProxy()->PlaySoundEx(sound, Vec3(0,0,0), FORWARD_DIRECTION, FLAG_SOUND_DEFAULT_3D, 255, 1.0f, radius);
			if (m_trailSoundId != INVALID_SOUNDID)
			{
				ISound *pSound=GetSoundProxy()->GetSound(m_trailSoundId);
				if (pSound)
					pSound->SetLoopMode(true);
			}
		}
	}
	else if (m_trailSoundId!=INVALID_SOUNDID)
	{
		GetSoundProxy()->StopSound(m_trailSoundId);
		m_trailSoundId=INVALID_SOUNDID;
	}
}

//------------------------------------------------------------------------
void CProjectile::WhizSound(bool enable, const Vec3 &pos)
{
	if (enable)
	{
		const IItemParamsNode *whiz=m_params->GetChild("whiz");
		if (!whiz)
			return;

		const char *sound=0;
		CItemParamReader reader(whiz);
		reader.Read("sound", sound);

		if (sound && sound[0] && GetISystem()->GetISoundSystem())
		{
			ISound *pSound=GetISystem()->GetISoundSystem()->CreateSound(sound, FLAG_SOUND_DEFAULT_3D);
			if (pSound)
			{
				m_whizSoundId = pSound->GetId();

				pSound->SetPosition(pos);
				pSound->Play();
			}
		}
	}
	else if (m_whizSoundId!=INVALID_SOUNDID)
	{
		GetSoundProxy()->StopSound(m_whizSoundId);
		m_whizSoundId=INVALID_SOUNDID;
	}
}

//------------------------------------------------------------------------
void CProjectile::TrailEffect(bool enable)
{
	if (enable)
	{
		const IItemParamsNode *trail=m_params->GetChild("trail");
		if (!trail)
			return;

		const char *effect=0;
		float scale=1.0f; 
		CItemParamReader reader(trail);
		reader.Read("effect", effect);
		reader.Read("scale", scale);

		if (effect && effect[0])
			m_trailEffectId = AttachEffect(true, 0, effect, Vec3(0,0,0), Vec3(0,1,0), scale);
	}
	else if (m_trailEffectId>=0)
	{
		AttachEffect(false, m_trailEffectId);
		m_trailEffectId=-1;
	}
}

//------------------------------------------------------------------------
int CProjectile::AttachEffect(bool attach, int id, const char *name, const Vec3 &offset, const Vec3 &dir, float scale)
{
	// m_trailEffectId is -1 for invalid, otherwise it's the slot number where the particle effect was loaded
	if (!attach)
	{
		if (id>=0)
			m_pEntity->FreeSlot(id);
	}
	else
	{
		IParticleEffect *pParticleEffect = GetISystem()->GetI3DEngine()->FindParticleEffect(name);
		if (!pParticleEffect)
			return -1;

		// find a free slot
		SEntitySlotInfo dummy;
		int i=0;
		while (m_pEntity->GetSlotInfo(i, dummy))
			i++;

		m_pEntity->LoadParticleEmitter(i, pParticleEffect, 0, -1, true);
		Matrix34 tm = IParticleEffect::ParticleLoc(offset, dir, scale);
		m_pEntity->SetSlotLocalTM(i, tm);

		return i;
	}

	return -1;
}

//------------------------------------------------------------------------
void CProjectile::Stick(EventPhysCollision *pCollision)
{
	assert(pCollision);
	int trgId = 1;
	int srcId = 0;
	IPhysicalEntity *pTarget = pCollision->pEntity[trgId];
	if (pTarget == m_pEntity->GetPhysics())
	{
		trgId = 0;
		srcId = 1;
		pTarget = pCollision->pEntity[trgId];
	}

	IEntity *pTargetEntity = pTarget ? GetISystem()->GetIEntitySystem()->GetEntityFromPhysics(pTarget) : 0;

	if (pTarget && (!pTargetEntity || (pTargetEntity->GetId() != m_ownerId)))
	{
		Vec3 an(m_pEntity->GetWorldTM().TransformVector(-FORWARD_DIRECTION));
		an.Normalize();
		float dot = an.Dot(pCollision->n);

		bool stick=true;
		if (m_pStickyBuddy)
		{
			for (int i=0;i<m_stuck;i++)
			{
				if (m_constraintspt[i].GetDistance(pCollision->pt)<0.175f)
				{
					stick=false;
					break;
				}
			}
		}

		if (stick && (!m_pStickyBuddy || m_pStickyBuddy==pTarget) && pCollision->penetration<0.025f && (!m_pStickyBuddy || dot>=0.85f))
		{
			if (!m_pStickyBuddy)
			{
				m_pEntity->SetTimer(ePTIMER_STICKY, STICKY_TIMER);

				pe_action_impulse ai;
				ai.iApplyTime=0;
				ai.impulse=-pCollision->n*m_mass;
				m_pPhysicalEntity->Action(&ai);
			}

			pe_action_add_constraint ac;
			ac.pBuddy = pTarget;
			ac.pt[0] = pCollision->pt;
			ac.pt[1] = pCollision->pt;
			ac.partid[0] = pCollision->partid[srcId];
			ac.partid[1] = pCollision->partid[trgId];

			m_pStickyBuddy = pTarget;
			m_constraints[m_stuck]=m_pPhysicalEntity->Action(&ac);
			m_constraintspt[m_stuck++]=pCollision->pt;
		}
		else
		{
			// if we reached maximum number of collisions without a stuck 
			if (++m_stickyCollisions>=MAX_STICKY_BADCOLLISIONS)
				Unstick();
		}
	}
}

//------------------------------------------------------------------------
void CProjectile::Unstick()
{
	for (int i=0;i<m_stuck;i++)
	{
		pe_action_update_constraint uc;
		uc.bRemove = 1;
		uc.idConstraint = m_constraints[i];
		m_pPhysicalEntity->Action(&uc);
	}

	m_stuck=0;
	m_stickyCollisions=0;
	m_pStickyBuddy=0;

	m_pEntity->KillTimer(ePTIMER_STICKY);
};

//------------------------------------------------------------------------
IEntitySoundProxy *CProjectile::GetSoundProxy()
{
	IEntitySoundProxy *pSoundProxy=static_cast<IEntitySoundProxy *>(m_pEntity->GetProxy(ENTITY_PROXY_SOUND));
	if (!pSoundProxy)
		pSoundProxy=static_cast<IEntitySoundProxy *>(m_pEntity->CreateProxy(ENTITY_PROXY_SOUND));

	assert(pSoundProxy);

	return pSoundProxy;
}

