/* ** 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::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 m_tImpact = dataMissileType->readyTime + (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::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) { 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) { 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(); if (m_target) { //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::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); }