/*
** Copyright (C) 1996, 1997 Microsoft Corporation. All Rights Reserved.
**
** File: missileIGC.cpp
**
** Author:
**
** Description:
** Implementation of the CmissileIGC class. This file was initially created by
** the ATL wizard for the core object.
**
** History:
*/
// missileIGC.cpp : Implementation of CmissileIGC
#include "pch.h"
#include "missileIGC.h"
/////////////////////////////////////////////////////////////////////////////
// CmissileIGC
CmissileIGC::CmissileIGC(void)
:
m_launcher(NULL),
m_target(NULL),
m_missileType(NULL)
{
}
CmissileIGC::~CmissileIGC(void)
{
}
HRESULT CmissileIGC::Initialize(ImissionIGC* pMission, Time now, const void* data, int dataSize)
{
TmodelIGC<ImissileIGC>::Initialize(pMission, now, data, dataSize);
ZRetailAssert (data && (dataSize == sizeof(DataMissileIGC)));
{
DataMissileIGC* dataMissile = (DataMissileIGC*)data;
m_missileType = dataMissile->pmissiletype;
assert (m_missileType);
assert (m_missileType->GetObjectType() == OT_missileType);
m_missileType->AddRef();
DataMissileTypeIGC* dataMissileType = (DataMissileTypeIGC*)(m_missileType->GetData());
assert (dataMissileType);
//Load the model for the missile
HRESULT hr;
if (iswalpha(dataMissileType->modelName[0])) {
hr = Load(0, dataMissileType->modelName, dataMissileType->textureName, dataMissileType->iconName,
c_mtDamagable | c_mtHitable | c_mtCastRay | c_mtSeenBySide);
GetThingSite()->SetAfterburnerSmokeSize ( 0.25f + (GetRadius () * 0.1f));
GetThingSite()->SetAfterburnerFireDuration (0.33f);
GetThingSite()->SetAfterburnerSmokeDuration (2.0f);
}
else
{
// , should pass in blend color from database
hr = LoadDecal(dataMissileType->textureName, dataMissileType->iconName,
Color((float)dataMissileType->color.r,
(float)dataMissileType->color.g,
(float)dataMissileType->color.b,
(float)dataMissileType->color.a),
dataMissileType->bDirectional,
dataMissileType->width,
c_mtDamagable | c_mtHitable | c_mtCastRay | c_mtSeenBySide,
c_htsSphere);
}
assert (SUCCEEDED(hr));
SetRadius(dataMissileType->radius);
SetSignature(dataMissileType->signature);
m_fraction = 1.0f;
SetPosition(dataMissile->position);
SetVelocity(dataMissile->velocity);
{
Orientation o(dataMissile->forward);
SetOrientation(o);
}
{
Rotation r(dataMissile->forward, dataMissileType->rotation);
SetRotation(r);
}
m_launcher = dataMissile->pLauncher;
assert (m_launcher);
m_launcher->AddRef();
{
HitTest* ht = GetHitTest();
//lifespan == 0 => immortal missile that can hit until it gets terminated on the next update; this is bad
assert (dataMissileType->lifespan > 0.0f);
m_timeExpire = now + dataMissileType->lifespan;
assert (m_timeExpire != now);
//Can't hit your launcher
ht->SetNoHit(m_launcher->GetHitTest());
SetSide(m_launcher->GetSide());
}
m_launcher->SetLastMissileFired(this);
m_timeActivate = now + dataMissileType->readyTime;
SetCluster(dataMissile->pCluster);
if (dataMissile->pTarget && (dataMissile->pTarget->GetCluster() == dataMissile->pCluster))
{
m_target = dataMissile->pTarget;
m_target->AddRef();
//Estimate the time of impact with the target
// mmf / Tkela modifications for zero accel
m_tImpact = dataMissileType->readyTime +
((dataMissileType->acceleration <= 0.0f)
? 0.0f
: (float)sqrt((2.0f / dataMissileType->acceleration) *
(m_target->GetPosition() -
dataMissile->position +
dataMissileType->readyTime * (m_target->GetVelocity() - dataMissile->velocity)).Length()));
}
m_lock = dataMissile->lock;
m_missileID = dataMissile->missileID;
m_bDisarmed = dataMissile->bDud;
SetMass(0.0f);
}
return S_OK;
}
void CmissileIGC::Terminate(void)
{
AddRef();
TmodelIGC<ImissileIGC>::Terminate();
if (m_launcher)
{
if (m_launcher->GetLastMissileFired() == this)
m_launcher->SetLastMissileFired(NULL);
m_launcher->Release();
m_launcher = NULL;
}
if (m_target)
{
m_target->Release();
m_target = NULL;
}
if (m_missileType)
{
m_missileType->Release();
m_missileType = NULL;
}
Release();
}
double NewtonA(double a, double b, double c, double d, double tOld)
{
//Solve:
// f(t) = a + bt + c t^2 - d t^4 = 0
// f'(t) = b + 2c t - 4d t^3
double t0;
double t1 = tOld;
//NYI not that we should need it ...
int nCycles = 0;
do
{
t0 = t1;
double p2 = t0 * t0; //t0^2
double p3 = p2 * t0; //t0^3
double p4 = p3 * t0; //t0^4
double f = a + b * t0 + c * p2 - d * p4;
if (fabs(f) < 0.1)
break; //close enough
double dfdt = b + 2.0 * c * t0 - 4.0 * d * p3;
double delta = f / dfdt;
t1 = t0 - delta;
}
while (++nCycles < 20);
if (nCycles >= 20)
{
// mmf commented this out, occurs way too often in 'normal' play, especially with quickfires
// we can stick it back in if someone wants to work on this code and needs this info
// debugf("**** Timeout NewtonA(%f %f %f %f %f) %f %f\n", a, b, c, d, tOld, t0, t1);
}
return t0;
}
double NewtonB(double a, double b, double c, double d, double tOld, double ti)
{
//Solve:
// f(t) = a + bt + c t^2 - d t^4 = 0
// f'(t) = b + 2c t - 4d t^3
//
// but, as a special case, quit if dfdt ever >= 0.0
double t0;
double t1 = tOld;
//NYI not that we should need it ...
int nCycles = 0;
do
{
t0 = t1;
double p2 = t0 * t0; //t0^2
double p3 = p2 * t0; //t0^3
double p4 = p3 * t0; //t0^4
double f = a + b * t0 + c * p2 - d * p4;
if (fabs(f) < 0.1)
break; //close enough
double dfdt = b + 2.0 * c * t0 - 4.0 * d * p3;
if (dfdt >= 0.0)
{
//debugf("**** Aborted NewtonB(%f %f %f %f %f) %f %f %f\n", a, b, c, d, dfdt, tOld, t0, t1);
return -1.0;
}
double delta = f / dfdt;
t1 = t0 - delta;
//This is not strictly required (if t is past the inflection point and the slope is negative,
//then there isn't any root before the inflection point to find) ... but it is better to start
//with a more accurate initial guess for t when searching for a root past the inflection point.
if (t1 > ti)
{
//debugf("**** Aborted NewtonB(%f %f %f %f %f) %f %f %f\n", a, b, c, d, dfdt, tOld, t0, t1);
return -1.0;
}
}
while (++nCycles < 20);
if (nCycles >= 20)
{
// mmf commented this out, occurs too often in 'normal' play, especially with quickfires
// we can stick it back in if someone wants to work on this code and needs this info
// debugf("**** Timeout NewtonA(%f %f %f %f %f) %f %f\n", a, b, c, d, tOld, t0, t1);
debugf("**** Timeout NewtonB(%f %f %f %f %f) %f %f\n", a, b, c, d, tOld, t0, t1);
}
return t0;
}
static float solve(const Vector& dP,
const Vector& dV,
float acceleration,
float tOld)
{
assert (acceleration > 0.0f);
//General problem when will the missile hit the target
// Solve |dP + t * dV| = 1/2 acceleration t^2
// F1(t) = dP * dP + 2 * t * dP * dV + t^2 * dV * dV = a + b t + c t^2
// F2(t) = 0.25 acceleration^2 * t^4 = d t^4
//Solve for t such that F1(t) == F2(t)
double a = dP * dP;
double c = dV * dV;
double d = acceleration * acceleration / 4.0;
if (c == 0.0)
{
//Let's eliminate one special case upfront: c = 0 implies a non-moving target (or no lock)
//Either way ... it makes solving the problem easy
// c = 0 => b = 0
// a = d t^4
// t = (a/d)^(1/4)
return float(pow(a/d, 0.25));
}
double b = 2.0 * (dP * dV);
//F(t) = F1(t) - F2(t) = a + b t + c t^2 - d t^4
//F'(t) = b + 2c t - 4d t^3
//F''(t) = 2c - 12 d t^2
//First ... find the inflection points ... F''(t) = 0
// since c, d > 0, there is exactly one > 0.0f
double ti = sqrt(c / (6.0 * d));
//What is the slope at the inflection point?
double dftp = b + (4.0/3.0) * c * ti; //Simplifying a little F'(ti) == b + 2c ti - 4d ti^3
const double epsilon = -0.001;
if (dftp <= epsilon)
{
//OK ... easy case: F'(t) at its local maximum for t > 0 is < 0
//Therefore F'(t) < 0 for all t > 0 and Newton's works very nicely ... use it
return float(NewtonA(a, b, c, d, tOld));
}
if (b < 0.0)
{
//OK ... hard case #1 ... F'(0) = b < 0 & F'(ti) > epsilon
//so there is a local minima in [0, ti] ... which means that there might be a solution
//in [0, tmn] where F'(tmn) = 0
//Unfortunately, tmn is not trivial to find, so we fake it:
//Pick t in [0, ti] such that F'(t) < 0
double t = tOld; //Try our initial guess at a solution
if ((t < ti) && (b + 2.0 * c * t - 4.0 * d * t * t * t > epsilon))
t = 0.0f; //Didn't work: fall back to 0 (which is known to be good)
//Use Newton's with a twist ... if F'(t) >= 0.0 then we can give up (because, barring round-off,
//we'll never overshoot a F(t) = 0 since F''(t) > 0)
t = NewtonB(a, b, c, d, t, ti);
if (t >= 0.0f)
return float(t);
}
//OK ... hard case #2 ... F'(ti) > epsilon
// so there is a local maxima in [ti, infinity] ... which means there might be a solution
// in [ti, tmx] and there will be one in [tmx, infinity], where F'(tmx) = 0
//
// Except that, since we got here, we are reasonably sure that the there is no solution in [ti, tmx]
// (since, if so, there would also be another solution in [0, tmn] & we didn't find it above)
//
// So ... try to find a solution starting at t, where F'(t) < epsilon
//Start past the inflection point
double t = tOld;
if (t < ti)
t = ti + 1.0;
//Search for the a time with a sufficiently negative slope
while (b + 2.0 * c * t - 4.0 * d * t * t * t > epsilon)
t += 1.0; //Keep incrementing till we get it right
//OK ... we found a good starting value.
// Because F''(t) < 0 for all t > ti, we don't have to worry about local maxima
return float(NewtonA(a, b, c, d, t));
}
void CmissileIGC::Update(Time now)
{
if (GetMyLastUpdate() >= m_timeExpire)
{
Explode(GetPosition());
Terminate();
}
else if (now >= GetMyLastUpdate())
{
if (now > m_timeActivate)
{
//Actively track the target
Orientation o = GetOrientation();
float acceleration = m_missileType->GetAcceleration();
float dt = now - ((m_timeActivate > GetMyLastUpdate()) ? m_timeActivate : GetMyLastUpdate());
if (m_target && (m_target->GetCluster() != GetCluster()))
{
m_target->Release();
m_target = NULL; //lost our target
}
const Vector& myPosition = GetPosition();
const Vector& myVelocity = GetVelocity();
// mmf / Tkela modifications for zero accel
if (m_target && (acceleration > 0.0f))
{
//Where will our target be when we get there ... try several passes
//adjusting our speed (to account for velocity).
Vector dP = m_target->GetPosition() - myPosition;
Vector dV = m_target->GetVelocity() * m_lock - myVelocity;
m_tImpact = solve(dP, dV, acceleration, m_tImpact);
o.TurnTo(dP + m_tImpact * dV, m_missileType->GetTurnRate() * dt * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaTurnRateMissile));
SetOrientation(o);
}
const Vector& backward = o.GetBackward();
//Kick in the primary thrusters
Vector deltaV = backward * (acceleration * dt);
SetVelocity(myVelocity - deltaV);
if (m_timeActivate > GetMyLastUpdate())
{
//The missile activated partway through the update cycle. Fudge things (by a teleport)
//so that the missile ends up at the position it would have ended up if the activate had
//happened exactly at an update boundary
SetPosition(myPosition - deltaV * (m_timeActivate - GetMyLastUpdate()));
}
m_tImpact -= dt;
{
Rotation r = GetRotation();
r.axis(o.GetBackward());
SetRotation(r);
}
assert (GetThingSite());
GetThingSite()->SetAfterburnerThrust(backward, 1.0f);
}
TmodelIGC<ImissileIGC>::Update(now);
}
}
void CmissileIGC::HandleCollision(Time timeCollision,
float tCollision,
const CollisionEntry& entry,
ImodelIGC* pModel)
{
if (!m_bDisarmed)
{
ObjectType ot = pModel->GetObjectType();
if (ot == OT_mine)
return; //Ignore collisions with minefields
IIgcSite* pigc = GetMyMission()->GetIgcSite();
Vector position1;
DamageTypeID dmgid = m_missileType->GetDamageType();
{
//A missile hit something
position1 = GetPosition() + GetVelocity() * tCollision;
Vector position2 = pModel->GetPosition() + pModel->GetVelocity() * tCollision;
if (ot != OT_missile)
{
ExpendableAbilityBitMask eabm = m_missileType->GetCapabilities();
if (eabm & c_eabmWarpBomb)
{
//Hack so that the client will not show an explosion
position1 = Vector::GetZero();
if (ot == OT_warp)
pigc->WarpBombEvent((IwarpIGC*)pModel, this);
}
else if (pModel->GetAttributes() & c_mtDamagable)
{
//The target can take damage
IdamageIGC* pDamage = (IdamageIGC*)pModel;
pDamage->ReceiveDamage(dmgid,
m_missileType->GetPower() * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaDamageMissiles),
timeCollision,
position2,
position1,
m_launcher);
if (eabm & c_eabmCapture)
{
//Hack so that the client will not show an explosion
position1 = Vector::GetZero();
if ((ot == OT_station) && m_launcher)
{
IstationIGC* pstation = (IstationIGC*)pModel;
if ((!GetMyMission()->GetMissionParams()->bInvulnerableStations) &&
(!pstation->GetStationType()->HasCapability(c_sabmPedestal)) &&
(pstation->GetShieldFraction() < GetMyMission()->GetFloatConstant(c_fcidDownedShield)))
{
pigc->CaptureStationEvent(m_launcher, pstation);
}
}
}
}
}
else
{
//Missiles hitting missiles are a special case: both die without calling either's receive damage method.
//Create explosions for both missiles
pigc->KillMissileEvent(((ImissileIGC*)pModel), position2);
}
}
pigc->KillMissileEvent(this, position1);
}
}
void CmissileIGC::Explode(const Vector& position)
{
//Boom
float p = m_missileType->GetBlastPower();
if (p != 0.0f)
{
GetCluster()->CreateExplosion(m_missileType->GetDamageType(),
p * GetSide()->GetGlobalAttributeSet().GetAttribute(c_gaDamageMissiles),
m_missileType->GetBlastRadius(),
c_etMissile,
GetMyLastUpdate(),
position,
m_launcher);
}
else
GetCluster()->GetClusterSite()->AddExplosion(position, GetRadius(), c_etMissile);
}