Beispiel #1
0
int CShieldClass::GetDamageAdj (CItemEnhancement Mods, const DamageDesc &Damage) const

//	GetDamageAdj
//
//	Damage adjustment

	{
	//	The adjustment varies by shield class

	int iAdj = m_DamageAdj.GetAdj(Damage.GetDamageType());

	//	Adjust based on difference in level (negative numbers means the shield
	//	is lower than the damage level):
	//
	//	...
	//	-3	=	4.5x damage
	//	-2	=	4x damage
	//	-1	=	3.5x damage
	//	0	=	3x damage
	//	1	=	2.5x damage
	//	2	=	2x damage
	//	3	=	1.5x damage
	//	>3	=	1x damage

	if (Damage.GetShieldDamageLevel())
		iAdj = iAdj * Max(100, 300 + (50 * (Damage.GetShieldDamageLevel() - GetLevel()))) / 100;

	//	Adjust based on enhancements

	if (Mods.IsNotEmpty())
		iAdj = iAdj * Mods.GetDamageAdj(Damage) / 100;

	return iAdj;
	}
Beispiel #2
0
CSpaceObject::DamageResults CMissile::Damage (CSpaceObject *pCause, const CVector &vHitPos, int iDirection, const DamageDesc &Damage)

//	Damage
//
//	Object takes damage from the given source

	{
	CSpaceObject *pAttacker = pCause->GetDamageCause();

	//	Compute damage

	bool bDestroy = false;
	int iDamage = Damage.RollDamage();
	if (iDamage == 0)
		return damageNoDamage;

	//	If this is a momentum attack then we are pushed

	int iMomentum;
	if (iMomentum = Damage.GetMomentumDamage())
		{
		CVector vAccel = PolarToVector(iDirection, -10 * iMomentum * iMomentum);
		Accelerate(vAccel, g_MomentumConstant);
		ClipSpeed(GetMaxSpeed());
		}

	//	Create a hit effect

	CEffectCreator *pEffect = g_pUniverse->FindEffectType(g_HitEffectUNID);
	if (pEffect)
		pEffect->CreateEffect(GetSystem(),
				this,
				vHitPos,
				GetVel());

	//	Take damage

	if (iDamage < m_iHitPoints)
		{
		m_iHitPoints -= iDamage;
		return damageArmorHit;
		}

	//	We are destroyed

	m_iHitPoints = 0;
	if (m_pDesc->m_iVaporTrailLength)
		{
		m_fDestroyed = true;
		m_iLifeLeft = m_pDesc->m_iVaporTrailLength;
		}
	else
		Destroy(killedByDamage, pAttacker);

	//	A missile might be able to pass through after hitting us

	return damagePassthrough;
	}
bool CParticleEffect::CanBeHitBy (const DamageDesc &Damage)

//	CanBeHitBy
//
//	Returns TRUE if the given type of damage can hit this object

	{
	//	Only massless particles can hit a particle field

	return (Damage.GetDamageType() == damageLaser 
			|| Damage.GetDamageType() == damageParticle
			|| Damage.GetDamageType() == damageIonRadiation
			|| Damage.GetDamageType() == damagePositron);
	}
Beispiel #4
0
bool CItemEnhancement::IsReflective (const DamageDesc &Damage, int *retiReflectChance) const

//	IsReflective
//
//	Returns TRUE if we reflect the given damage

{
    switch (GetType())
    {
    case etReflect:
    {
        if (!IsDisadvantage() && Damage.GetDamageType() == GetDamageType())
        {
            if (retiReflectChance)
                *retiReflectChance = 50 + (GetLevel() * 5);

            return true;
        }
        else
            return false;
    }

    default:
        return false;
    }
}
Beispiel #5
0
int CArmorClass::GetDamageAdj (CItemEnhancement Mods, const DamageDesc &Damage)

//	GetDamageAdj
//
//	Returns the damage adjustment for the given damage type

	{
	int iDamageAdj = GetDamageAdj(Damage.GetDamageType());

	if (Mods.IsNotEmpty())
		return iDamageAdj * Mods.GetDamageAdj(Damage) / 100;
	else
		return iDamageAdj;
	}
Beispiel #6
0
int CShieldClass::GetDamageAdj (CItemEnhancement Mods, const DamageDesc &Damage) const

//	GetDamageAdj
//
//	Damage adjustment

	{
	//	The adjustment varies by shield class

	int iAdj = (Damage.GetDamageType() == damageGeneric ? 100 : m_iDamageAdj[Damage.GetDamageType()]);

	//	Adjust based on the type of damage

	if (Damage.GetShieldDamage())
		iAdj = iAdj * Max(100, 300 + (50 * (Damage.GetShieldDamageLevel() - GetLevel()))) / 100;

	//	Adjust based on enhancements

	if (Mods.IsNotEmpty())
		iAdj = iAdj * Mods.GetDamageAdj(Damage) / 100;

	return iAdj;
	}
Beispiel #7
0
int CItemEnhancement::GetDamageAdj (const DamageDesc &Damage) const

//	GetDamageAdj
//
//	Returns the damage adjustment confered by this mod

{
    switch (GetType())
    {
    case etResist:
        return Level2DamageAdj(GetLevel(), IsDisadvantage());

    case etResistEnergy:
        return (Damage.IsEnergyDamage() ? Level2DamageAdj(GetLevel(), IsDisadvantage()) : 100);

    case etResistMatter:
        return (Damage.IsMatterDamage() ? Level2DamageAdj(GetLevel(), IsDisadvantage()) : 100);

    case etResistByLevel:
    {
        if (Damage.GetDamageType() == GetDamageType()
                || Damage.GetDamageType() == GetDamageType() + 1)
            return Level2DamageAdj(GetLevel(), IsDisadvantage());
        else
            return 100;
    }

    case etResistByDamage:
        return (Damage.GetDamageType() == GetDamageType() ? Level2DamageAdj(GetLevel(), IsDisadvantage()) : 100);

    case etResistByDamage2:
    {
        if (Damage.GetDamageType() == GetDamageType())
            //	0 = 100			100
            //	1 = 90			111
            //	2 = 80			125
            //	3 = 70			143
            return Level2DamageAdj(GetLevel(), IsDisadvantage());
        else if (Damage.GetDamageType() == GetDamageType() + 2)
            //	0 = 100			100
            //	1 = 95			105
            //	2 = 90			112
            //	3 = 85			121
            return 100 + ((Level2DamageAdj(GetLevel(), IsDisadvantage()) - 100) / 2);
        else
            return 100;
    }

    default:
        return 100;
    }
}
Beispiel #8
0
int CItemEnhancement::GetAbsorbAdj (const DamageDesc &Damage) const

//	GetAbsorbAdj
//
//	Returns damage absorbed

{
    switch (GetType())
    {
    case etReflect:
    {
        if (IsDisadvantage() && Damage.GetDamageType() == GetDamageType())
            return Level2DamageAdj(GetLevel());
        else
            return 100;
    }

    default:
        return 100;
    }
}
Beispiel #9
0
bool CArmorClass::IsReflective (CItemCtx &ItemCtx, const DamageDesc &Damage)

//	IsReflective
//
//	Returns TRUE if the armor reflects this damage

	{
	const CItemEnhancement &Mods = ItemCtx.GetMods();

	int iReflectChance = 0;

	//	Base armor chance

	if (m_Reflective.InSet(Damage.GetDamageType()))
		iReflectChance = MAX_REFLECTION_CHANCE;

	//	Mods

	int iModReflect;
	if (Mods.IsNotEmpty() && Mods.IsReflective(Damage, &iModReflect))
		iReflectChance = Max(iReflectChance, iModReflect);

	//	Done

	if (iReflectChance)
		{
		CInstalledArmor *pSect = ItemCtx.GetArmor();

		int iMaxHP = GetMaxHP(ItemCtx);
		int iHP = (pSect ? pSect->GetHitPoints() : iMaxHP);

		//	Adjust based on how damaged the armor is

		iReflectChance = (iMaxHP > 0 ? iHP * iReflectChance / iMaxHP : iReflectChance);

		return (mathRandom(1, 100) <= iReflectChance);
		}
	else
		return false;
	}
Beispiel #10
0
int CArmorClass::CalcArmorDamageAdj (const DamageDesc &Damage) const

//	CalcArmorDamageAdj
//
//	Adjust for special armor damage:
//
//	<0	=	2.5x damage
//	0	=	2x damage
//	1	=	1.5x damage
//	2	=	1.25x damage
//	>2	=	1x damage

	{
	int iDamageLevel = Damage.GetArmorDamageLevel();
	if (iDamageLevel <= 0)
		return 100;

	int iDiff = m_pItemType->GetLevel() - iDamageLevel;

	switch (iDiff)
		{
		case 0:
			return 200;

		case 1:
			return 150;

		case 2:
			return 125;

		default:
			if (iDiff < 0)
				return 250;
			else
				return 100;
		}
	}
ALERROR CParticleEffect::Create (CSystem *pSystem,
								 CXMLElement *pDesc,
								 const CVector &vPos,
								 const CVector &vVel,
								 CParticleEffect **retpEffect)

//	Create
//
//	Create a new particle effect in the system

	{
	ALERROR error;
	CParticleEffect *pParticles;

	pParticles = new CParticleEffect;
	if (pParticles == NULL)
		return ERR_MEMORY;

	pParticles->Place(vPos, vVel);
	pParticles->SetObjectDestructionHook();

	pParticles->m_sName = pDesc->GetAttribute(NAME_ATTRIB);

	//	Create the type based on the descriptor

	SParticleType *pType = new SParticleType;
	pType->m_fWake = true;

	pType->m_fMaxRadius = true;
	pType->rRadius = pDesc->GetAttributeInteger(RADIUS_ATTRIB) * LIGHT_SECOND;
	pType->rHoleRadius = pDesc->GetAttributeInteger(MIN_RADIUS_ATTRIB) * LIGHT_SECOND;
	
	int iDampening = pDesc->GetAttributeInteger(DAMPENING_ATTRIB);
	if (iDampening > 0)
		pType->rDampening = iDampening / 1000.0;
	else
		pType->rDampening = 0.9999;

	pType->rAveSpeed = LIGHT_SPEED * 0.1;

	//	Load damage

	CString sDamage;
	if (pDesc->FindAttribute(DAMAGE_ATTRIB, &sDamage))
		{
		SDesignLoadCtx Ctx;
		DamageDesc Damage;
		if (error = Damage.LoadFromXML(Ctx, sDamage))
			return error;

		pType->pDamageDesc = new CWeaponFireDesc;
		pType->pDamageDesc->InitFromDamage(Damage);
		pType->m_fFreeDesc = true;
		}

	//	Set the image

	pType->wColor = CG16bitImage::RGBValue(0, 255, 0);
	CXMLElement *pImage = pDesc->GetContentElementByTag(IMAGE_TAG);
	if (pImage)
		{
		pType->iPaintStyle = paintImage;
		pType->Image.InitFromXML(pImage);
		}

	//	Miscelleanous settings

	pParticles->SetBounds(pType->rRadius);

	//	Create the group

	SParticleArray *pGroup;
	int iCount = pDesc->GetAttributeInteger(COUNT_ATTRIB);

	pParticles->CreateGroup(pType, iCount, &pGroup);

	//	Add to system

	if (error = pParticles->AddToSystem(pSystem))
		{
		delete pParticles;
		return error;
		}

	//	Done

	if (retpEffect)
		*retpEffect = pParticles;

	return NOERROR;
	}
void CParticleEffect::OnReadFromStream (SLoadCtx &Ctx)

//	OnReadFromStream
//
//	Read object data from a stream
//
//	CString		m_sName
//	DWORD		m_pAnchor (CSpaceObject ref)
//
//	DWORD		type: iPaintStyle (or 0xffffffff if no more groups)
//	Image		type: Image
//	DWORD		type: wColor
//	DWORD		type: iRegenerationTimer
//	DWORD		type: iLifespan
//	Metric		type: rAveSpeed
//	DWORD		type: iDirection
//	DWORD		type: iDirRange
//	Metric		type: rRadius
//	Metric		type: rHoleRadius
//	Metric		type: rDampening
//	DamageDesc	type: Damage
//	DWORD		type: flags
//
//	DWORD		array: iAlive 
//
//	DWORD		particle: iDestiny
//	DWORD		particle: iLifeLeft
//	Vector		particle: vPos
//	Vector		particle: vVel

	{
#ifdef DEBUG_LOAD
	::OutputDebugString("CParticleEffect::OnReadFromStream\n");
#endif
	m_sName.ReadFromStream(Ctx.pStream);
	Ctx.pSystem->ReadObjRefFromStream(Ctx, &m_pAnchor);

	DWORD dwLoad;
	DWORD dwNext;
	Ctx.pStream->Read((char *)&dwNext, sizeof(DWORD));
	SParticleArray *pLastGroup = NULL;
	while (dwNext != 0xffffffff)
		{
		//	Create a new group

		SParticleArray *pGroup = new SParticleArray;
		if (pLastGroup == NULL)
			m_pFirstGroup = pGroup;
		else
			pLastGroup->pNext = pGroup;

		//	Create a new type

		pGroup->pType = new SParticleType;
		pGroup->pType->iPaintStyle = dwNext;
		pGroup->pType->Image.ReadFromStream(Ctx);
		Ctx.pStream->Read((char *)&dwLoad, sizeof(DWORD));
		pGroup->pType->wColor = (WORD)dwLoad;
		Ctx.pStream->Read((char *)&pGroup->pType->iRegenerationTimer, sizeof(DWORD));
		Ctx.pStream->Read((char *)&pGroup->pType->iLifespan, sizeof(DWORD));
		Ctx.pStream->Read((char *)&pGroup->pType->rAveSpeed, sizeof(Metric));
		Ctx.pStream->Read((char *)&pGroup->pType->iDirection, sizeof(DWORD));
		Ctx.pStream->Read((char *)&pGroup->pType->iDirRange, sizeof(DWORD));
		Ctx.pStream->Read((char *)&pGroup->pType->rRadius, sizeof(Metric));
		Ctx.pStream->Read((char *)&pGroup->pType->rHoleRadius, sizeof(Metric));
		Ctx.pStream->Read((char *)&pGroup->pType->rDampening, sizeof(Metric));
		DamageDesc Damage;
		Damage.ReadFromStream(Ctx);

		//	Load type flags

		Ctx.pStream->Read((char *)&dwLoad, sizeof(DWORD));
		pGroup->pType->m_fMaxRadius =	((dwLoad & 0x00000001) ? true : false);
		pGroup->pType->m_fLifespan =	((dwLoad & 0x00000002) ? true : false);
		pGroup->pType->m_fWake =		((dwLoad & 0x00000004) ? true : false);
		pGroup->pType->m_fRegenerate =	((dwLoad & 0x00000008) ? true : false);
		pGroup->pType->m_fDrag =		((dwLoad & 0x00000010) ? true : false);
		bool bHasDamage =				((dwLoad & 0x00000020) ? true : false);

		//	Init damage desc

		if (bHasDamage)
			{
			pGroup->pType->pDamageDesc = new CWeaponFireDesc;
			pGroup->pType->pDamageDesc->InitFromDamage(Damage);
			pGroup->pType->m_fFreeDesc = true;
			}

		//	Load array of particles

		Ctx.pStream->Read((char *)&dwLoad, sizeof(DWORD));
		pGroup->iAlive = dwLoad;
		pGroup->iCount = dwLoad;
		pGroup->pParticles = new SParticle [pGroup->iCount];

		for (int i = 0; i < pGroup->iAlive; i++)
			{
			Ctx.pStream->Read((char *)&pGroup->pParticles[i].iDestiny, sizeof(DWORD));
			Ctx.pStream->Read((char *)&pGroup->pParticles[i].iLifeLeft, sizeof(DWORD));
			Ctx.pStream->Read((char *)&pGroup->pParticles[i].vPos, sizeof(CVector));
			Ctx.pStream->Read((char *)&pGroup->pParticles[i].vVel, sizeof(CVector));
			}

		//	Next

		pGroup->pNext = NULL;
		pLastGroup = pGroup;
		Ctx.pStream->Read((char *)&dwNext, sizeof(DWORD));
		}
	}
void CParticleEffect::OnWriteToStream (IWriteStream *pStream)

//	OnWriteToStream
//
//	Write the object's data to stream
//
//	CString		m_sName
//	DWORD		m_pAnchor (CSpaceObject ref)
//
//	DWORD		type: iPaintStyle (or 0xffffffff if no more groups)
//	Image		type: Image
//	DWORD		type: wColor
//	DWORD		type: iRegenerationTimer
//	DWORD		type: iLifespan
//	Metric		type: rAveSpeed
//	DWORD		type: iDirection
//	DWORD		type: iDirRange
//	Metric		type: rRadius
//	Metric		type: rHoleRadius
//	Metric		type: rDampening
//	DamageDesc	type: Damage
//	DWORD		type: flags
//
//	DWORD		array: iAlive 
//
//	DWORD		particle: iDestiny
//	DWORD		particle: iLifeLeft
//	Vector		particle: vPos
//	Vector		particle: vVel

	{
	DWORD dwSave;

	m_sName.WriteToStream(pStream);
	WriteObjRefToStream(m_pAnchor, pStream);

	//	Save each group

	SParticleArray *pGroup = m_pFirstGroup;
	while (pGroup)
		{
		SParticleType *pType = pGroup->pType;

		//	Save the type information

		pStream->Write((char *)&pType->iPaintStyle, sizeof(DWORD));
		pType->Image.WriteToStream(pStream);
		dwSave = pType->wColor;
		pStream->Write((char *)&dwSave, sizeof(DWORD));
		pStream->Write((char *)&pType->iRegenerationTimer, sizeof(DWORD));
		pStream->Write((char *)&pType->iLifespan, sizeof(DWORD));
		pStream->Write((char *)&pType->rAveSpeed, sizeof(Metric));
		pStream->Write((char *)&pType->iDirection, sizeof(DWORD));
		pStream->Write((char *)&pType->iDirRange, sizeof(DWORD));
		pStream->Write((char *)&pType->rRadius, sizeof(Metric));
		pStream->Write((char *)&pType->rHoleRadius, sizeof(Metric));
		pStream->Write((char *)&pType->rDampening, sizeof(Metric));
		if (pType->pDamageDesc)
			pType->pDamageDesc->m_Damage.WriteToStream(pStream);
		else
			{
			DamageDesc Dummy;
			Dummy.WriteToStream(pStream);
			}

		DWORD dwSave = 0;
		dwSave |= (pType->m_fMaxRadius ?	0x00000001 : 0);
		dwSave |= (pType->m_fLifespan ?		0x00000002 : 0);
		dwSave |= (pType->m_fWake ?			0x00000004 : 0);
		dwSave |= (pType->m_fRegenerate ?	0x00000008 : 0);
		dwSave |= (pType->m_fDrag ?			0x00000010 : 0);
		dwSave |= (pType->pDamageDesc ?		0x00000020 : 0);
		pStream->Write((char *)&dwSave, sizeof(DWORD));

		//	Save the array of particles

		pStream->Write((char *)&pGroup->iAlive, sizeof(DWORD));
		int iCount = pGroup->iAlive;

		SParticle *pParticle = pGroup->pParticles;
		SParticle *pEnd = pParticle + pGroup->iCount;
		while (pParticle < pEnd)
			{
			if (pParticle->IsValid())
				{
				pStream->Write((char *)&pParticle->iDestiny, sizeof(DWORD));
				pStream->Write((char *)&pParticle->iLifeLeft, sizeof(DWORD));
				pStream->Write((char *)&pParticle->vPos, sizeof(CVector));
				pStream->Write((char *)&pParticle->vVel, sizeof(CVector));
				
				iCount--;
				}

			pParticle++;
			}

		//	iAlive better match the actual count
		ASSERT(iCount == 0);

		pGroup = pGroup->pNext;
		}

	//	Mark the end with 0xffffffff

	dwSave = 0xffffffff;
	pStream->Write((char *)&dwSave, sizeof(DWORD));
	}
Beispiel #14
0
void CMissile::OnUpdate (Metric rSecondsPerTick)

//	OnUpdate
//
//	Update the beam

	{
	//	If we're already destroyed, then just update the timer until the
	//	vapor trail fades out

	if (m_fDestroyed)
		{
		//	Update the painter

		if (m_pPainter)
			{
			m_pPainter->OnUpdate();

			RECT rcRect;
			m_pPainter->GetRect(&rcRect);
			SetBounds(rcRect);
			}

		//	Done?

		if (--m_iLifeLeft <= 0)
			{
			Destroy(removedFromSystem, NULL);
			return;
			}
		}

	//	Otherwise, update

	else
		{
		int i;
		CSystem *pSystem = GetSystem();
		int iTick = m_iTick + GetDestiny();
		bool bDestroy = false;

		//	Accelerate, if necessary

		if (m_pDesc->m_iAccelerationFactor > 0 
				&& (iTick % 10 ) == 0)
			{
			if (m_pDesc->m_iAccelerationFactor < 100
					|| GetVel().Length() < m_pDesc->m_rMaxMissileSpeed)
				SetVel(GetVel() * (Metric)(m_pDesc->m_iAccelerationFactor / 100.0));
			}

		//	If we can choose new targets, see if we need one now

		if (m_pDesc->CanAutoTarget() && m_pTarget == NULL)
			m_pTarget = GetNearestEnemy(MAX_TARGET_RANGE, false);

		//	If this is a tracking missile, change direction to face the target

		if ((m_pDesc->m_iManeuverability > 0)
				&& m_pTarget 
				&& ((iTick % m_pDesc->m_iManeuverability) == 0))
			{
			//	Get the position and velocity of the target

			CVector vTarget = m_pTarget->GetPos() - GetPos();
			CVector vTargetVel = m_pTarget->GetVel() - GetVel();

			//	Figure out which direction to move in

			Metric rCurrentSpeed = GetVel().Length();
			Metric rTimeToIntercept = CalcInterceptTime(vTarget, vTargetVel, rCurrentSpeed);
			if (rTimeToIntercept > 0.0)
				{
				CVector vInterceptPoint = vTarget + vTargetVel * rTimeToIntercept;
				int iFireAngle = VectorToPolar(vInterceptPoint, NULL);

				//	If we are directional, then we are constrained to specific angles

				if (m_pDesc->m_bDirectional)
					{
					if (!AreAnglesAligned(iFireAngle, m_iRotation, g_RotationAngle / 2))
						{
						int iTurn = (iFireAngle + 360 - m_iRotation) % 360;

						if (iTurn >= 180)
							m_iRotation = (m_iRotation + 360 - g_RotationAngle) % 360;
						else
							m_iRotation = (m_iRotation + g_RotationAngle) % 360;

						}
					}
				else
					{
					if (!AreAnglesAligned(iFireAngle, m_iRotation, 1))
						{
						int iTurn = (iFireAngle + 360 - m_iRotation) % 360;

						if (iTurn >= 180)
							{
							int iTurnAngle = Min((360 - iTurn), g_RotationAngle);
							m_iRotation = (m_iRotation + 360 - iTurnAngle) % 360;
							}
						else
							{
							int iTurnAngle = Min(iTurn, g_RotationAngle);
							m_iRotation = (m_iRotation + iTurnAngle) % 360;
							}

						SetVel(PolarToVector(m_iRotation, rCurrentSpeed));
						}
					}

				SetVel(PolarToVector(m_iRotation, rCurrentSpeed));
				}
			}

		//	Update exhaust

		if (m_pExhaust)
			{
			if (iTick % m_pDesc->m_iExhaustRate)
				{
				if (m_pExhaust->GetCount() == m_pExhaust->GetMaxCount())
					m_pExhaust->Dequeue();

				SExhaustParticle &New = m_pExhaust->GetAt(m_pExhaust->Queue());
				New.vPos = GetPos();
				New.vVel = GetVel();
				}

			for (int i = 0; i < m_pExhaust->GetCount(); i++)
				{
				SExhaustParticle &Particle = m_pExhaust->GetAt(i);
				Particle.vVel = m_pDesc->m_rExhaustDrag * Particle.vVel;
				Particle.vPos = Particle.vPos + Particle.vVel * g_SecondsPerUpdate;
				}
			}

		//	Update the painter

		if (m_pPainter)
			{
			m_pPainter->OnUpdate();

			RECT rcRect;
			m_pPainter->GetRect(&rcRect);
			SetBounds(rcRect);
			}

		//	If we have a vapor trail and need to save rotation, do it

		if (m_pDesc->m_iVaporTrailLength 
				&& m_pDesc->m_iManeuverability)
			{
			//	Compute the current rotation

			int iDirection;
			if (m_pDesc->m_bDirectional)
				iDirection = (AlignToRotationAngle(m_iRotation) + 180) % 360;
			else
				iDirection = (m_iRotation + 180) % 360;

			//	Add the current rotation to the list of saved rotations

			if (m_pSavedRotations == NULL)
				{
				m_pSavedRotations = new int [m_pDesc->m_iVaporTrailLength];
				m_iSavedRotationsCount = 0;
				}

			int iStart = Min(m_iSavedRotationsCount, m_pDesc->m_iVaporTrailLength - 1);
			for (i = iStart; i > 0; i--)
				m_pSavedRotations[i] = m_pSavedRotations[i - 1];

			m_pSavedRotations[0] = iDirection;
			if (m_iSavedRotationsCount < m_pDesc->m_iVaporTrailLength)
				m_iSavedRotationsCount++;
			}

		//	See if the missile hit anything

		if (m_fDetonate && m_pDesc->HasFragments())
			{
			CreateFragments(GetPos());
			CreateHitEffect(GetPos());
			bDestroy = true;
			}
		else if (m_pHit)
			{
			//	If we have fragments, then explode now

			if (m_iHitDir == -1
					&& m_pDesc->HasFragments()
					&& m_iTick >= m_pDesc->m_iProximityFailsafe)
				{
				CreateFragments(m_vHitPos);
				CreateHitEffect(m_vHitPos);
				bDestroy = true;
				}

			//	Otherwise, if this was a direct hit, then we do damage

			else if (m_iHitDir != -1)
				{
				DamageResults result;
				DamageDesc Damage = m_pDesc->m_Damage;
				Damage.AddBonus(m_iBonus);
				Damage.SetCause(m_iCause);
				if (IsAutomatedWeapon())
					Damage.SetAutomatedWeapon();

				result = m_pHit->Damage(this,
						m_vHitPos,
						(m_iHitDir + 360 + mathRandom(0, 30) - 15) % 360,
						Damage);

				//	If we hit another missile (or some small object) there is a chance
				//	that we continue

				if (result == damagePassthrough)
					{
					m_iHitPoints = m_iHitPoints / 2;
					bDestroy = (m_iHitPoints == 0);
					}

				//	Set the missile to destroy itself after a hit

				else if (m_pDesc->m_iPassthrough == 0
						|| result == damageNoDamage 
						|| result == damageAbsorbedByShields
						|| mathRandom(1, 100) > m_pDesc->m_iPassthrough)
					bDestroy = true;

				CreateHitEffect(m_vHitPos);
				}
			}

		//	See if the missile has faded out

		if (bDestroy || --m_iLifeLeft <= 0)
			{
			//	If this is a fragmentation weapon, then we explode at the end of life

			if (!bDestroy && m_pDesc->HasFragments())
				{
				CreateFragments(GetPos());
				CreateHitEffect(GetPos());
				}

			//	If we've got a vapor trail effect, then keep the missile object alive
			//	but mark it destroyed

			int iFadeLife;
			if (m_pDesc->m_iVaporTrailLength)
				{
				m_fDestroyed = true;
				m_iLifeLeft = m_pDesc->m_iVaporTrailLength;
				}

			//	If we've got an effect that needs time to fade out, then keep
			//	the missile object alive

			else if (m_pPainter && (iFadeLife = m_pPainter->GetFadeLifetime()))
				{
				m_pPainter->OnBeginFade();

				m_fDestroyed = true;
				m_iLifeLeft = iFadeLife;
				}

			//	Otherwise, destroy the missile

			else
				{
				Destroy(removedFromSystem, NULL);
				return;
				}
			}
		}

	m_iTick++;
	}