// ----------------------------------------------------------------------- //
//
// MODULE  : LOFProjectile.cpp
//
// PURPOSE : LOF Projectile classes - implementation
//
// CREATED : 8th November 1998 by Ewen Vowels for Anarchy-Arts
//
// ----------------------------------------------------------------------- //

#include "RiotWeapons.h"
#include "LOFProjectiles.h"
#include "LOFProxMineTrigger.h"
#include "RiotMsgIDs.h"

#include "RiotProjectiles.h"
#include "RiotObjectUtilities.h"
#include "DamageTypes.h"
#include "SurfaceTypes.h"
#include "SurfaceFunctions.h"

#define LOF_FIRE_ANI_STR	"Fire"
#define LOF_FLIGHT_ANI_STR	"Flight"
#define LOF_ARMED_ANI_STR	"Armed"
#define LOF_IMPACT_ANI_STR	"Impact"

#define UPDATE_DELTA	 	0.05f

// the speed at which the mortar projectiles fall to earth (we don't use the gravity flag FLAG_GRAVITY)
#define LOF_MORT_GRAVITY	-50.0f  
// the speed at which the proximity mines projectiles fall to earth (we don't use the gravity flag FLAG_GRAVITY)
#define LOF_PMINE_GRAVITY	-100.0f


BEGIN_CLASS(CAmbedHWARProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedHWARProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedHWARProjectile::CAmbedHWARProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedHWARProjectile::CAmbedHWARProjectile() : CProjectile()
{
	m_fExplosionDuration	= 1.5f;	
	m_eDamageType			= DT_EXPLODE;
	m_dwCantDamageTypes		= DT_EXPLODE;

	VEC_SET(m_vDims,  5.0f, 5.0f, 15.0f);
	VEC_SET(m_vScale, 2.0f, 2.0f, 2.0f);
	m_pProjectileFilename	= "Models\\PV_Weapons\\HWAR_Projectile.abc";
	m_pProjectileSkin		= "Skins\\Weapons\\HWAR_Projectile_a.dtx";
}


BEGIN_CLASS(CAmbedMortProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedMortProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortProjectile::CAmbedMortProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedMortProjectile::CAmbedMortProjectile() : CProjectile()
{
	m_fExplosionDuration	= 1.5f;	
	m_eDamageType			= DT_EXPLODE;
	m_dwCantDamageTypes		= DT_EXPLODE;

	VEC_SET(m_vPseudoGravity, 0.0f, LOF_MORT_GRAVITY, 0.0f);

	VEC_SET(m_vDims,  17.5f, 17.5f, 17.5f);
	VEC_SET(m_vScale, 0.15f, 0.15f, 0.15f);
	m_pProjectileFilename	= "Models\\PV_Weapons\\MORT_Projectile.abc";
	m_pProjectileSkin		= "Skins\\Weapons\\Ambed_MORT_pv_a.dtx";
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedMortProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			SetType(OT_MODEL);
		}
		break;

		case MID_UPDATE:
		{
			Update();
		}
		break;

		default : break;
	}

	return CProjectile::EngineMessageFn(messageID, pData, fData);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortProjectile::Update
//
//	PURPOSE:	Handle inflight logic etc. for this projectile
//
// ----------------------------------------------------------------------- //

void CAmbedMortProjectile::Update()
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE) return;

	pServerDE->SetNextUpdate(m_hObject, UPDATE_DELTA);
	DFLOAT fTime = pServerDE->GetTime();

	DVector vVel;
	pServerDE->GetVelocity(m_hObject, &vVel);

	if (m_bFirstSnake) // don't subtract the pseudo gravity until the second update
	{
		m_bFirstSnake = DFALSE;
	}
	else // make the projectile 'fall' to earth
	{
		VEC_ADD(vVel, vVel, m_vPseudoGravity);
		pServerDE->SetVelocity(m_hObject, &vVel);
	}

	// If we didn't hit anything, fragment...
	if (fTime >= (m_fStartTime + m_fLifeTime)) 
	{
		DRotation rRot;
		pServerDE->GetObjectRotation(m_hObject, &rRot);

		DVector vPos;
		pServerDE->GetObjectPos(m_hObject, &vPos);

		// Create the fragment projectiles...

		for (int i = 0; i <= 12; i++)
		{		
			ObjectCreateStruct theStruct;
			INIT_OBJECTCREATESTRUCT(theStruct);

			ROT_COPY(theStruct.m_Rotation, rRot);
			VEC_COPY(theStruct.m_Pos, vPos);
			theStruct.m_Flags = GUN_AMBEDMORTFRAG_ID; 

			CProjectile* pBullet;
			HCLASS hClass = pServerDE->GetClass("CAmbedMortFragmentProjectile");

			if (hClass)
			{
				pBullet = (CProjectile*)pServerDE->CreateObject(hClass, &theStruct);
			}

			// potentially dangerous ptr conversion
			// the setup method allows us to transfer vital information 
			// eg velocity, rotation etc. to the Fragment so that it will continue
			// in the correct direction.
			((CAmbedMortFragmentProjectile*)pBullet)->Setup(this, this->GetFiredFrom());

		} // end for loop

		// since we have fragmented, get rid of the main projectile
		RemoveObject(); 
	} 
}

// I had originally planned to make this a subclass of CAmbedMortProjectile
// but the functionality of the two classes was differenct enough to justify 
BEGIN_CLASS(CAmbedMortFragmentProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedMortFragmentProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortFragmentProjectile::CAmbedMortFragmentProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedMortFragmentProjectile::CAmbedMortFragmentProjectile()
{
	m_fExplosionDuration	= 0.75f;	
	m_fDamage				= 100.0f;
	m_fVelocity				= 500.0f;
	m_fRadius				= (DFLOAT)100.0f; // explosion radius
	m_fRange				= 750.0f;
	m_fLifeTime				= 4.5f;
	m_eDamageType			= DT_EXPLODE;
	m_dwCantDamageTypes		= DT_EXPLODE;
	m_bSilenced				= DFALSE;

	m_pProjectileFilename	= "Models\\PV_Weapons\\MORT_Projectile.abc";
	m_pProjectileSkin		= "Skins\\Weapons\\Ambed_MORT_pv_a.dtx";

	VEC_SET(m_vPseudoGravity, 0.0f, LOF_MORT_GRAVITY, 0.0f);

	VEC_SET(m_vDims,  10.0f, 10.0f, 10.0f);
	VEC_SET(m_vScale, 0.075f, 0.075f, 0.075f);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortFragmentProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedMortFragmentProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			SetType(OT_MODEL);
		}
		break;

		case MID_UPDATE:
		{
			Update();
		}
		break;

		default : break;
	}

	return CProjectile::EngineMessageFn(messageID, pData, fData);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortFragmentProjectile::Setup
//
//	PURPOSE:	Called by another CProjectile to setup directional vectors etc.
//
// ----------------------------------------------------------------------- //

void CAmbedMortFragmentProjectile::Setup(CProjectile* pParent, HOBJECT hFiredFrom)
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE || !pParent || !hFiredFrom) return;

	pServerDE->GetVelocity(pParent->m_hObject, &m_vDir);
	m_hFiredFrom	= hFiredFrom;
	m_nId			= GUN_AMBEDMORT_ID;

	DRotation rRot;
	DVector vU, vR, vF;

	pServerDE->GetObjectRotation(hFiredFrom, &rRot);
	pServerDE->GetRotationVectors(&rRot, &vU, &vR, &vF);
	
	// add some randomness to the trajectory of this projectile
	DFLOAT m_nPerturbe = 250.0f, rPerturbe, uPerturbe, fPerturbe;
	rPerturbe = ((DFLOAT)GetRandom(-m_nPerturbe, m_nPerturbe));
	uPerturbe = ((DFLOAT)GetRandom(-m_nPerturbe, m_nPerturbe));
	fPerturbe = ((DFLOAT)GetRandom(-m_nPerturbe, m_nPerturbe));

	DVector vRand;
	VEC_SET(vRand, rPerturbe, uPerturbe, fPerturbe);

	VEC_ADD(m_vDir, m_vDir, vRand);
	VEC_NORM(m_vDir);

	// If we have a fired from object, make a link to it...

	if (m_hFiredFrom)
	{
		pServerDE->CreateInterObjectLink(m_hObject, m_hFiredFrom);
	}


	// Set our force ignore limit and mass...

	pServerDE->SetForceIgnoreLimit(m_hObject, 0.0f);
	pServerDE->SetObjectMass(m_hObject, m_fMass);

	// Make sure we are pointing in the direction we are traveling...

	// Spin doesn't really matter, so make it random...

	DVector vSpin;
	VEC_SET(vSpin, GetRandom(0.1f, 0.9f), GetRandom(0.1f, 0.9f), GetRandom(0.1f, 0.9f)); 

	//DRotation rRot;
	pServerDE->AlignRotation(&rRot, &m_vDir, &vSpin);
	pServerDE->SetObjectRotation(m_hObject, &rRot);

		
	// And away we go...

	DVector vVel;
	VEC_MULSCALAR(vVel, m_vDir, m_fVelocity);
	pServerDE->SetVelocity(m_hObject, &vVel);


	// Special case of 0 life time...

	if (m_fLifeTime <= 0.0f) 
	{
		Detonate(DNULL);
	}
	else
	{
		AddSpecialFX();
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortFragmentProjectile::Update
//
//	PURPOSE:	Handle inflight logic etc. for this projectile
//
// ----------------------------------------------------------------------- //

void CAmbedMortFragmentProjectile::Update()
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE) return;

	pServerDE->SetNextUpdate(m_hObject, UPDATE_DELTA);

	DFLOAT fTime = pServerDE->GetTime();

	DVector vVel;
	pServerDE->GetVelocity(m_hObject, &vVel);

	// add pseudo gravity to make this fragment fall to earth
	VEC_ADD(vVel, vVel, m_vPseudoGravity);
	pServerDE->SetVelocity(m_hObject, &vVel);

	// If we didn't hit anything, explode...
	if (fTime >= (m_fStartTime + m_fLifeTime)) 
	{
		Detonate(DNULL);
	} 
}
// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedMortFragmentProjectile::Detonate()
//
//	PURPOSE:	Handle blowing up the projectile
//
// ----------------------------------------------------------------------- //
#pragma message ( "LOFFIXME - Optimise the CAmbedMortFragmentProjectile::Detonate() method " __FILE__ )
void CAmbedMortFragmentProjectile::Detonate(HOBJECT hObj)
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE || m_bDetonated) return;

	m_bDetonated = DTRUE;

	SurfaceType eType = ST_UNKNOWN;

	DVector vPos;
	pServerDE->GetObjectPos(m_hObject, &vPos);

	// Determine the normal of the surface we are impacting on...

	DVector vNormal;
	VEC_SET(vNormal, 0.0f, 1.0f, 0.0f);

	if (hObj)
	{
		HCLASS hClassObj   = pServerDE->GetObjectClass(hObj);
		HCLASS hClassWorld = pServerDE->GetObjectClass(pServerDE->GetWorldObject());

		if (pServerDE->IsKindOf(hClassObj, hClassWorld))
		{
			CollisionInfo info;
			pServerDE->GetLastCollision(&info);

			if (info.m_hPoly)
			{
				eType = GetSurfaceType(info.m_hPoly);
			}

			VEC_COPY(vNormal, info.m_Plane.m_Normal);

			DRotation rRot;
			pServerDE->AlignRotation(&rRot, &vNormal, DNULL);
			pServerDE->SetObjectRotation(m_hObject, &rRot);

			// Calculate where we really hit the plane...

			DVector vVel, vP0, vP1;
			pServerDE->GetVelocity(m_hObject, &vVel);

			VEC_COPY(vP1, vPos);
			VEC_MULSCALAR(vVel, vVel, pServerDE->GetFrameTime());
			VEC_SUB(vP0, vP1, vVel);

			DFLOAT fDot1 = DIST_TO_PLANE(vP0, info.m_Plane);
			DFLOAT fDot2 = DIST_TO_PLANE(vP1, info.m_Plane);

			if (fDot1 < 0.0f && fDot2 < 0.0f || fDot1 > 0.0f && fDot2 > 0.0f)
			{
				VEC_COPY(vPos, vP1);
			}
			else
			{
				DFLOAT fPercent = -fDot1 / (fDot2 - fDot1);
				VEC_LERP(vPos, vP0, vP1, fPercent);
			}
		}
	}
	else
	{
		// Since hObj was null, this means the projectile's lifetime was up,
		// so we just blew-up in the air.

		eType = ST_AIR; 
	}


	if (eType == ST_UNKNOWN)
	{
		eType = GetSurfaceType(hObj);
	}


	AddImpact(hObj, vPos, vNormal, eType);

	
	// See if this projectile does impact damage...

	if (hObj && (m_fDamage > 0.0f && m_fRadius <= 0.0f))
	{
		HOBJECT hDamager = m_hFiredFrom ? m_hFiredFrom : m_hObject;
		DamageObject(hDamager, this, hObj, m_fDamage, m_vDir, m_eDamageType);
	}


	// Remove projectile from world...

	RemoveObject();
}


BEGIN_CLASS(CAmbedEMSProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedEMSProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedEMSProjectile::CAmbedEMSProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedEMSProjectile::CAmbedEMSProjectile() : CProjectile()
{
	m_fExplosionDuration	= .35f; // new ev - 3/12/98 was 0.5f
	m_eDamageType			= DT_ENERGY;
	m_pProjectileFilename	= "Sprites\\AmbedEMS.spr";

	m_dwCantDamageTypes		= DT_ENERGY;

	VEC_SET(m_vDims, 1.0f, 1.0f, 1.0f);
	VEC_SET(m_vScale, 0.09f, 0.09f, 0.70f); // new ev - 3/12/98 was 0.15f, 0.15f, 1.0f
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedEMSProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedEMSProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT lData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			// SetType(OT_SPRITE);
		}
		break;

		case MID_INITIALUPDATE:
		{
			CServerDE* pServerDE = BaseClass::GetServerDE();
			if (!pServerDE) return 0;

			pServerDE->SetObjectColor(m_hObject, 1.0f, 1.0f, 1.0f, 1.0f);
			break;
		}
	}

	return CProjectile::EngineMessageFn(messageID, pData, lData);
}

BEGIN_CLASS(CAmbedShochiProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedShochiProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedShochiProjectile::CAmbedShochiProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedShochiProjectile::CAmbedShochiProjectile() : CProjectile()
{
	m_eDamageType			= DT_ENERGY;
	m_dwCantDamageTypes		= DT_ENERGY;
	m_pProjectileFilename	= "Sprites\\Shochi.spr";

	VEC_SET(m_vDims,  1.0f, 1.0f, 1.0f);
	VEC_SET(m_vScale, 0.1f, 0.1f, 0.6f);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedShochiProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedShochiProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT lData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			// SetType(OT_SPRITE);
		}
		break;

		case MID_INITIALUPDATE:
		{
			CServerDE* pServerDE = BaseClass::GetServerDE();
			if (!pServerDE) return 0;

			pServerDE->SetObjectColor(m_hObject, 1.0f, 1.0f, 1.0f, 1.0f);
			break;
		}
	}

	return CProjectile::EngineMessageFn(messageID, pData, lData);
}

BEGIN_CLASS(CAmbedProximityMineProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedProximityMineProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::CAmbedProximityMineProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedProximityMineProjectile::CAmbedProximityMineProjectile() : CProjectile()
{
	// I had gravity as a flag but it introduced ugly jittering once the grenade
	// was stuck to a wall / floor!
	m_dwFlags				|= FLAG_SHADOW /*| FLAG_GRAVITY*/;

	m_fExplosionDuration	= 1.0f;
	m_eDamageType			= DT_EXPLODE;
	m_nId					= GUN_AMBEDPROXIMITYMINE_ID;

	m_pProjectileFilename	= "Models\\PV_Weapons\\AmbedProximityProjectile.abc";
	m_pProjectileSkin		= "Skins\\Weapons\\AmbedProximityProjectile_a.dtx";

	m_bAttached				= DFALSE;
	m_bArmed				= DFALSE;
	m_fArmTime				= 5.0f;

	m_pThudSound			= "Sounds\\Weapons\\Spider\\Thud.wav";
	m_pArmSound				= "Sounds\\Weapons\\AmbedProximityMine\\Plant_Mine.wav";

	VEC_SET(m_vDims, 9.0f, 9.0f, 9.0f);
	VEC_SET(m_vScale, 3.3f, 3.3f, 3.3f);

	m_dwFireAni				= INVALID_ANI;
	m_dwArmedAni			= INVALID_ANI;
	m_dwImpactAni			= INVALID_ANI;

	m_hExternalBox			= DNULL;

	VEC_SET(m_vPseudoGravity, 0.0f, LOF_PMINE_GRAVITY, 0.0f);

	VEC_INIT(m_vPosOffset);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedProximityMineProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			SetType(OT_MODEL);
		}
		break;

		case MID_INITIALUPDATE:
		{
			if (fData == INITIALUPDATE_SAVEGAME) break;

			CServerDE* pServerDE = BaseClass::GetServerDE();
			if (!pServerDE) break;

			// Set up anis...

			m_dwFireAni		= pServerDE->GetAnimIndex(m_hObject, LOF_FIRE_ANI_STR);
			m_dwArmedAni	= pServerDE->GetAnimIndex(m_hObject, LOF_ARMED_ANI_STR);
			m_dwImpactAni	= pServerDE->GetAnimIndex(m_hObject, LOF_IMPACT_ANI_STR);

			// Start the projecitle fire animation...

			if (m_dwFireAni != INVALID_ANI)
			{
				pServerDE->SetModelLooping(m_hObject, DFALSE);
				pServerDE->SetModelAnimation(m_hObject, m_dwFireAni);
			}
		}
		break;

		case MID_UPDATE:
		{
			Update();
		}
		break;

		case MID_SAVEOBJECT:
		{
			Save((HMESSAGEWRITE)pData, (DDWORD)fData);
		}
		break;

		case MID_LOADOBJECT:
		{
			Load((HMESSAGEREAD)pData, (DDWORD)fData);
		}
		break;

		default : break;
	}

	return CProjectile::EngineMessageFn(messageID, pData, fData);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::HandleImpact
//
//	PURPOSE:	Handle hitting something
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedProximityMineProjectile::ObjectMessageFn(HOBJECT hSender, DDWORD messageID, HMESSAGEREAD hRead)
{
	switch (messageID)
	{
		// detonate when told to by our ProxMineTrigger
		case MID_PROXMINE_TOUCHED :
		{
			CServerDE* pServerDE = BaseClass::GetServerDE();
			if (pServerDE)
			{
				HOBJECT hToucher = pServerDE->ReadFromMessageObject(hRead);
				Detonate(hToucher);
			}
		}
		break;

	}

	return BaseClass::ObjectMessageFn(hSender, messageID, hRead);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::HandleImpact
//
//	PURPOSE:	Handle hitting something
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::HandleImpact(HOBJECT hObj)
{
	CServerDE* pServerDE = BaseClass::GetServerDE();

	// only process this message if we are attached and armed or not attached!
	if (!pServerDE || (m_bAttached && !m_bArmed)) return;

	DVector vPos;

	// Set the impact ani...

	if (m_dwImpactAni != INVALID_ANI)
	{
		pServerDE->SetModelLooping(m_hObject, DTRUE);
		pServerDE->SetModelAnimation(m_hObject, m_dwImpactAni);
	}

	
	// If we are attached to a character, hide the grenade...

	if (hObj)
	{
		HCLASS hClassObj   = pServerDE->GetObjectClass(hObj);
		HCLASS hClassWorld = pServerDE->GetObjectClass(pServerDE->GetWorldObject());

		DVector vMyPos;

		if (pServerDE->IsKindOf(hClassObj, hClassWorld))
		{
			// we are on a world object now so attach
			m_bAttached = DTRUE; 

			// Align with surface normal...
			
			CollisionInfo info;
			pServerDE->GetLastCollision(&info);

			DVector vNormal;
			VEC_NEGATE(vNormal, info.m_Plane.m_Normal);

			DRotation rRot;
			pServerDE->AlignRotation(&rRot, &vNormal, DNULL);
			pServerDE->SetObjectRotation(m_hObject, &rRot);
		}
		else
		{
			// we hit a non world object (an AI or player) so explode now
			Detonate(hObj);
			return;
		}
		// Keep track of our position relative to the world...

		pServerDE->GetObjectPos(m_hObject, &vMyPos);
		VEC_COPY(m_vPosOffset, vMyPos);
	}


	// Play thud sound...

	if (m_bAttached)
	{
		//pServerDE->BPrint("THUD");
		if (m_pThudSound) 
		{
			pServerDE->GetObjectPos( m_hObject, &vPos );
			// LOFFIXME - reduce thud sound to a more reasonable distance
#pragma message ( "LOFFIXME - reduce thud sound to a more reasonable distance file "  __FILE__ )
			PlaySoundFromPos( &vPos, m_pThudSound, 2000.0f, SOUNDPRIORITY_MISC_MEDIUM);
		}
		return;
	}
	CProjectile::HandleImpact(hObj);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::Update()
//
//	PURPOSE:	Do update
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::Update()
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE) return;

	DVector vVel;
	pServerDE->GetVelocity(m_hObject, &vVel);

	if (m_bFirstSnake) // don't subtract the pseudo gravity until the second update
	{
		m_bFirstSnake = DFALSE;
	}
	else // make the projectile 'fall' to earth
	{
		VEC_ADD(vVel, vVel, m_vPseudoGravity);
		pServerDE->SetVelocity(m_hObject, &vVel);
	}


	if (!m_bArmed && ((pServerDE->GetTime() - m_fStartTime) > m_fArmTime))
	{
		// we are not armed and our time to activate / arm the projectile is up
		DVector vPos;
		m_bArmed = DTRUE;

		if (m_pArmSound)
		{
			pServerDE->GetObjectPos( m_hObject, &vPos );
			PlaySoundFromPos( &vPos, m_pArmSound, 1000.0f, SOUNDPRIORITY_MISC_MEDIUM);
		}
	
		// create the external bounding box that any AI or players can touch
		// if they are touched then the ProxMineTrigger sends a message telling
		// us to explode!
		if (!m_hExternalBox)
		{
			CreateExternalBox();
		}
	}

	if (m_bAttached)
	{
		DVector vZero;
		VEC_INIT(vZero);

		pServerDE->SetVelocity(m_hObject, &vZero);
		pServerDE->SetAcceleration(m_hObject, &vZero);

		// make sure we stay put!
		pServerDE->SetObjectPos(m_hObject, &m_vPosOffset);
	}

	if (m_bArmed)
	{
		// See if we need to update the ani...

		DDWORD dwState  = pServerDE->GetModelPlaybackState(m_hObject);
		DDWORD dwAni	= pServerDE->GetModelAnimation(m_hObject);

		if (m_dwArmedAni && dwAni != m_dwArmedAni)
		{
			if (dwState & MS_PLAYDONE)
			{
				pServerDE->SetModelLooping(m_hObject, DTRUE);
				pServerDE->SetModelAnimation(m_hObject, m_dwArmedAni);
			}
		}
	}
}
// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::CreateExternalBox()
//
//	PURPOSE:	Create an external bounding box
//
// ----------------------------------------------------------------------- //
	
void CAmbedProximityMineProjectile::CreateExternalBox()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return;

	ObjectCreateStruct theStruct;
	INIT_OBJECTCREATESTRUCT(theStruct);

	DVector vPos;
	pServerDE->GetObjectPos(m_hObject, &vPos);
	VEC_COPY(theStruct.m_Pos, vPos);

	HCLASS hClass = pServerDE->GetClass("ProxMineTrigger");
	LPBASECLASS pExTrigger = pServerDE->CreateObject(hClass, &theStruct);

	if (pExTrigger)
	{
		m_hExternalBox = pExTrigger->m_hObject;

		// send a message to the new ProxMineTrigger giving them our object for
		// future message passing reference. ie. so they can detonate us later!
		HMESSAGEWRITE hMsg = pServerDE->StartMessageToObject(this, m_hExternalBox, MID_PROXMINE_INIT);
		pServerDE->WriteToMessageObject(hMsg, m_hObject);
		pServerDE->EndMessage(hMsg);
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::RemoveObject()
//
//	PURPOSE:	Remove the object, and do clean up (isle 4)
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::RemoveObject()
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE) return;

	// make sure that we clean up the bounding box / ProxMineTrigger!
	if (m_hExternalBox)
	{
		pServerDE->RemoveObject(m_hExternalBox);
		m_hExternalBox = DNULL;
	}

	CProjectile::RemoveObject();
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::Save
//
//	PURPOSE:	Save the object
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::Save(HMESSAGEWRITE hWrite, DDWORD dwSaveFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hWrite) return;

	pServerDE->WriteToMessageVector(hWrite, &m_vPosOffset);
	pServerDE->WriteToMessageByte(hWrite,  m_bArmed);
	pServerDE->WriteToMessageByte(hWrite,  m_bAttached);
	pServerDE->WriteToMessageFloat(hWrite, m_fArmTime);
	pServerDE->WriteToMessageDWord(hWrite, m_dwFireAni);
	pServerDE->WriteToMessageDWord(hWrite, m_dwArmedAni);
	pServerDE->WriteToMessageDWord(hWrite, m_dwImpactAni);
}



// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::Load
//
//	PURPOSE:	Load the object
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::Load(HMESSAGEREAD hRead, DDWORD dwLoadFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hRead) return;

	pServerDE->ReadFromMessageVector(hRead, &m_vPosOffset);
	m_bArmed		= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_bAttached		= (DBOOL) pServerDE->ReadFromMessageByte(hRead);
	m_fArmTime		= pServerDE->ReadFromMessageFloat(hRead);
	m_dwFireAni		= pServerDE->ReadFromMessageDWord(hRead);
	m_dwArmedAni	= pServerDE->ReadFromMessageDWord(hRead);
	m_dwImpactAni	= pServerDE->ReadFromMessageDWord(hRead);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::HandleTouch()
//
//	PURPOSE:	Handle touch notify message
//
// ----------------------------------------------------------------------- //

void CAmbedProximityMineProjectile::HandleTouch(HOBJECT hObj)
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE || m_bObjectRemoved) return;

	// Let it get out of our bounding box... but only before it is armed!

	if (hObj == m_hFiredFrom && !m_bArmed) return;


	// See if we want to impact on this object...

	DDWORD dwUsrFlags = pServerDE->GetObjectUserFlags(hObj);
	if (dwUsrFlags & USRFLG_IGNORE_PROJECTILES) return;


	HCLASS hClassMe		= pServerDE->GetObjectClass(m_hObject);
	HCLASS hClassObj	= pServerDE->GetObjectClass(hObj);
	HCLASS hClassWorld  = pServerDE->GetObjectClass(pServerDE->GetWorldObject());


	// Don't impact on non-solid objects...

	DDWORD dwFlags = pServerDE->GetObjectFlags(hObj);
	if (!pServerDE->IsKindOf(hClassObj, hClassWorld) && !(dwFlags & FLAG_SOLID)) return;

	// See if we hit the sky...

	if (pServerDE->IsKindOf(hClassObj, hClassWorld))
	{
		CollisionInfo info;
		pServerDE->GetLastCollision(&info);

		SurfaceType eType = GetSurfaceType(info.m_hPoly);

		if (eType == ST_SKY)
		{
			RemoveObject();
			return;
		}
	}

// if we make it this far we want to POP whatever triggered this message!
//	Detonate(hObj);
	HandleImpact(hObj);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedProximityMineProjectile::Detonate()
//
//	PURPOSE:	Handle blowing up the projectile
//
// ----------------------------------------------------------------------- //
#pragma message ( "LOFFIXME - Optimise the CAmbedProximityMineProjectile::Detonate() method " __FILE__ )
void CAmbedProximityMineProjectile::Detonate(HOBJECT hObj)
{
	CServerDE* pServerDE = BaseClass::GetServerDE();
	if (!pServerDE || m_bDetonated) return;

	m_bDetonated = DTRUE;

	SurfaceType eType = ST_UNKNOWN;

	DVector vPos;
	pServerDE->GetObjectPos(m_hObject, &vPos);

	// Determine the normal of the surface we are impacting on...

	DVector vNormal;
	VEC_SET(vNormal, 0.0f, 1.0f, 0.0f);

	if (hObj)
	{
		HCLASS hClassObj   = pServerDE->GetObjectClass(hObj);
		HCLASS hClassWorld = pServerDE->GetObjectClass(pServerDE->GetWorldObject());

		if (pServerDE->IsKindOf(hClassObj, hClassWorld))
		{
			CollisionInfo info;
			pServerDE->GetLastCollision(&info);

			if (info.m_hPoly)
			{
				eType = GetSurfaceType(info.m_hPoly);
			}

			VEC_COPY(vNormal, info.m_Plane.m_Normal);

			DRotation rRot;
			pServerDE->AlignRotation(&rRot, &vNormal, DNULL);
			pServerDE->SetObjectRotation(m_hObject, &rRot);

			// Calculate where we really hit the plane...

			DVector vVel, vP0, vP1;
			pServerDE->GetVelocity(m_hObject, &vVel);

			VEC_COPY(vP1, vPos);
			VEC_MULSCALAR(vVel, vVel, pServerDE->GetFrameTime());
			VEC_SUB(vP0, vP1, vVel);

			DFLOAT fDot1 = DIST_TO_PLANE(vP0, info.m_Plane);
			DFLOAT fDot2 = DIST_TO_PLANE(vP1, info.m_Plane);

			if (fDot1 < 0.0f && fDot2 < 0.0f || fDot1 > 0.0f && fDot2 > 0.0f)
			{
				VEC_COPY(vPos, vP1);
			}
			else
			{
				DFLOAT fPercent = -fDot1 / (fDot2 - fDot1);
				VEC_LERP(vPos, vP0, vP1, fPercent);
			}
		}
	}
	else
	{
		// Since hObj was null, this means the projectile's lifetime was up,
		// so we just blew-up in the air.

		eType = ST_AIR; 
	}


	if (eType == ST_UNKNOWN)
	{
		eType = GetSurfaceType(hObj);
	}


	AddImpact(hObj, vPos, vNormal, eType);

	
	// See if this projectile does impact damage...

	if (hObj && (m_fDamage > 0.0f && m_fRadius <= 0.0f))
	{
		HOBJECT hDamager = m_hFiredFrom ? m_hFiredFrom : m_hObject;
		DamageObject(hDamager, this, hObj, m_fDamage, m_vDir, m_eDamageType);
	}


	// Remove projectile from world...

	RemoveObject();
}


BEGIN_CLASS(CAmbedKatoBurstProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedKatoBurstProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedKatoBurstProjectile::CAmbedKatoBurstProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedKatoBurstProjectile::CAmbedKatoBurstProjectile() : CProjectile()
{
	m_fExplosionDuration	= .5f;
	m_eDamageType			= DT_ENERGY;
	m_pProjectileFilename	= "Sprites\\KatoBurst.spr";

	m_dwCantDamageTypes		= DT_ENERGY;

	VEC_SET(m_vDims, 1.0f, 1.0f, 1.0f);
	VEC_SET(m_vScale, 0.15f, 0.15f, 1.0f);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedKatoBurstProjectile::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD CAmbedKatoBurstProjectile::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT lData)
{
	switch(messageID)
	{
		case MID_PRECREATE:
		{
			// SetType(OT_SPRITE);
		}
		break;

		case MID_INITIALUPDATE:
		{
			CServerDE* pServerDE = BaseClass::GetServerDE();
			if (!pServerDE) return 0;

			pServerDE->SetObjectColor(m_hObject, 1.0f, 1.0f, 1.0f, 1.0f);
			break;
		}
	}

	return CProjectile::EngineMessageFn(messageID, pData, lData);
}

/*
BEGIN_CLASS(CAmbedDSGProjectile)
END_CLASS_DEFAULT_FLAGS(CAmbedDSGProjectile, CProjectile, NULL, NULL, CF_HIDDEN)

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	CAmbedDSGProjectile::CAmbedDSGProjectile
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

CAmbedDSGProjectile::CAmbedDSGProjectile() : CProjectile()
{
	m_eDamageType			= DT_PUNCTURE;
	m_dwCantDamageTypes		= DT_PUNCTURE;

	VEC_SET(m_vDims,  1.0f, 1.0f, 1.0f);
	VEC_SET(m_vScale, 1.0f, 1.0f, 1.0f);
}
*/
