void CParticleDamage::OnUpdate (SUpdateCtx &Ctx, Metric rSecondsPerTick) // OnUpdate // // Update { m_iTick++; // Update the single particle painter if (m_pPainter) m_pPainter->OnUpdate(); // Set up context block for particle array update SEffectUpdateCtx EffectCtx; EffectCtx.pSystem = GetSystem(); EffectCtx.pObj = this; EffectCtx.pDamageDesc = m_pDesc; EffectCtx.iTotalParticleCount = m_iParticleCount; EffectCtx.pEnhancements = m_pEnhancements; EffectCtx.iCause = m_iCause; EffectCtx.bAutomatedWeapon = IsAutomatedWeapon(); EffectCtx.Attacker = m_Source; // Update (includes doing damage) m_Particles.Update(EffectCtx); // If we're tracking, change velocity to follow target if (m_pTarget && m_pDesc->IsTrackingTime(m_iTick)) m_Particles.UpdateTrackTarget(m_pTarget, m_pDesc->GetManeuverRate(), m_pDesc->GetRatedSpeed()); // Expired? if (--m_iLifeLeft <= 0) { Destroy(removedFromSystem, CDamageSource()); return; } // Emit new particles if (m_iTick < m_iEmitTime && !m_Source.IsEmpty()) { InitParticles(m_pDesc->GetParticleCount(), m_vEmitSourcePos - GetPos(), GetVel(), m_iEmitDirection); } }
void CBeam::OnUpdate (SUpdateCtx &Ctx, Metric rSecondsPerTick) // OnUpdate // // Update the beam { DEBUG_TRY bool bDestroy = false; m_iTick++; // See if the beam hit anything if (m_pHit) { // Tell the object hit that it has been damaged SDamageCtx Ctx; Ctx.pObj = m_pHit; Ctx.pDesc = m_pDesc; Ctx.Damage = m_pDesc->m_Damage; Ctx.Damage.AddBonus(m_iBonus); Ctx.Damage.SetCause(m_iCause); if (IsAutomatedWeapon()) Ctx.Damage.SetAutomatedWeapon(); Ctx.iDirection = (m_iHitDir + 360 + mathRandom(0, 30) - 15) % 360; Ctx.vHitPos = m_vPaintTo; Ctx.pCause = this; Ctx.Attacker = m_Source; EDamageResults result = m_pHit->Damage(Ctx); // Set the beam to destroy itself after a hit if (m_pDesc->GetPassthrough() == 0 || result == damageNoDamage || result == damageAbsorbedByShields || mathRandom(1, 100) > m_pDesc->GetPassthrough()) bDestroy = true; } // See if the beam has faded out if (bDestroy || --m_iLifeLeft <= 0) Destroy(removedFromSystem, CDamageSource()); DEBUG_CATCH }
void CMissile::CreateFragments (const CVector &vPos) // CreateFragments // // Create fragments { // If there is an event, then let it handle the fragmentation if (m_pDesc->FireOnFragment(m_Source, this, vPos, m_pHit, m_pTarget)) return; // NOTE: Missile fragments don't inherit the velocity of the missile // (otherwise, fragmentation weapons explode too late to do much damage) if (m_pDesc->HasFragments()) GetSystem()->CreateWeaponFragments(m_pDesc, m_iBonus, m_iCause, m_Source, m_pTarget, vPos, CVector(), this); // Create the hit effect SDamageCtx Ctx; Ctx.pObj = NULL; Ctx.pDesc = m_pDesc; Ctx.Damage = m_pDesc->m_Damage; Ctx.Damage.AddBonus(m_iBonus); Ctx.Damage.SetCause(m_iCause); if (IsAutomatedWeapon()) Ctx.Damage.SetAutomatedWeapon(); Ctx.iDirection = mathRandom(0, 359); Ctx.vHitPos = vPos; Ctx.pCause = this; Ctx.Attacker = m_Source; m_pDesc->CreateHitEffect(GetSystem(), Ctx); }
void CMissile::OnUpdate (SUpdateCtx &Ctx, 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(); // LATER: We shouldn't have to update bounds here because // it is set in OnMove. SetBounds(m_pPainter); } // Done? if (--m_iLifeLeft <= 0) { Destroy(removedFromSystem, CDamageSource()); 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->IsTrackingTime(iTick) && m_pTarget) { // 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 int iFireAngle; Metric rCurrentSpeed = GetVel().Length(); Metric rTimeToIntercept = CalcInterceptTime(vTarget, vTargetVel, rCurrentSpeed); if (rTimeToIntercept > 0.0) { CVector vInterceptPoint = vTarget + vTargetVel * rTimeToIntercept; iFireAngle = VectorToPolar(vInterceptPoint, NULL); } else iFireAngle = VectorToPolar(vTarget); // Turn to desired direction. if (!AreAnglesAligned(iFireAngle, m_iRotation, 1)) { int iTurn = (iFireAngle + 360 - m_iRotation) % 360; if (iTurn >= 180) { int iTurnAngle = Min((360 - iTurn), m_pDesc->GetManeuverRate()); m_iRotation = (m_iRotation + 360 - iTurnAngle) % 360; } else { int iTurnAngle = Min(iTurn, m_pDesc->GetManeuverRate()); m_iRotation = (m_iRotation + iTurnAngle) % 360; } } 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(); // LATER: We shouldn't have to update bounds here because // it is set in OnMove. SetBounds(m_pPainter); } // If we have a vapor trail and need to save rotation, do it if (m_pDesc->GetVaporTrailLength() && m_pDesc->IsTracking()) { // Compute the current rotation int 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->GetVaporTrailLength()]; m_iSavedRotationsCount = 0; } int iStart = Min(m_iSavedRotationsCount, m_pDesc->GetVaporTrailLength() - 1); for (i = iStart; i > 0; i--) m_pSavedRotations[i] = m_pSavedRotations[i - 1]; m_pSavedRotations[0] = iDirection; if (m_iSavedRotationsCount < m_pDesc->GetVaporTrailLength()) m_iSavedRotationsCount++; } // See if the missile hit anything if (m_fDetonate && m_pDesc->ProximityBlast()) { CreateFragments(GetPos()); bDestroy = true; } else if (m_pHit) { // If we have fragments, then explode now if (m_iHitDir == -1 && m_pDesc->ProximityBlast() && m_iTick >= m_pDesc->GetProximityFailsafe()) { CreateFragments(m_vHitPos); bDestroy = true; } // Otherwise, if this was a direct hit, then we do damage else if (m_iHitDir != -1) { SDamageCtx DamageCtx; DamageCtx.pObj = m_pHit; DamageCtx.pDesc = m_pDesc; DamageCtx.Damage = m_pDesc->m_Damage; DamageCtx.Damage.AddBonus(m_iBonus); DamageCtx.Damage.SetCause(m_iCause); if (IsAutomatedWeapon()) DamageCtx.Damage.SetAutomatedWeapon(); DamageCtx.iDirection = (m_iHitDir + 360 + mathRandom(0, 30) - 15) % 360; DamageCtx.vHitPos = m_vHitPos; DamageCtx.pCause = this; DamageCtx.Attacker = m_Source; EDamageResults result = m_pHit->Damage(DamageCtx); // If we hit another missile (or some small object) there is a chance // that we continue if (result == damagePassthrough || result == damagePassthroughDestroyed) { m_iHitPoints = m_iHitPoints / 2; bDestroy = (m_iHitPoints == 0); } // Set the missile to destroy itself after a hit, if we did not // pass through else if (!m_fPassthrough) bDestroy = true; } } // 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->ProximityBlast()) CreateFragments(GetPos()); // If we've got a vapor trail effect, then keep the missile object alive // but mark it destroyed int iFadeLife; if (m_pDesc->GetVaporTrailLength()) { m_fDestroyed = true; m_iLifeLeft = m_pDesc->GetVaporTrailLength(); } // 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, CDamageSource()); return; } } } m_iTick++; }
void CParticleEffect::OnUpdate (SUpdateCtx &Ctx, Metric rSecondsPerTick) // OnUpdate // // Update the effect { int iTick = GetSystem()->GetTick() + GetDestiny(); // Do not bother updating everything if we are far from the POV bool bFarAway = false; if (g_pUniverse->GetPOV() && g_pUniverse->GetCurrentSystem() == GetSystem()) { Metric rPOVDist2 = (GetPos() - g_pUniverse->GetPOV()->GetPos()).Length2(); Metric rMaxUpdateDist2 = LIGHT_SECOND * LIGHT_SECOND * 3600; bFarAway = (rPOVDist2 > rMaxUpdateDist2); } // Update the particles SParticleArray *pGroup = m_pFirstGroup; while (pGroup) { SParticleType *pType = pGroup->pType; // Max distance for a particle in this group Metric rMaxDist2 = pType->rRadius * pType->rRadius; Metric rMinDist2 = pType->rHoleRadius * pType->rHoleRadius; // If the particle field causes damage then we need to // compute its average density int iDensity = 0; if (pType->pDamageDesc) { Metric rRadius2 = pType->rRadius * pType->rRadius; Metric rArea = rRadius2 / (LIGHT_SECOND * LIGHT_SECOND); iDensity = (int)(4 * pGroup->iCount / rArea); } // Get an array of objects in the particle field that // may influence the particles CSpaceObject *Objects[ctMaxObjsInField]; int iObjCount = 0; if (!bFarAway && (pType->m_fWake || pType->pDamageDesc)) { Metric rMaxInfluenceDist2 = rMaxDist2; for (int i = 0; i < GetSystem()->GetObjectCount(); i++) { CSpaceObject *pObj = GetSystem()->GetObject(i); if (pObj && pObj->GetCategory() == catShip && !pObj->IsInactive() && pObj->CanBeHit() && !pObj->IsDestroyed() && pObj != this) { CVector vDist = GetPos() - pObj->GetPos(); Metric rDist2 = vDist.Length2(); if (rDist2 < rMaxInfluenceDist2 && (pObj->GetVel().Length2() > g_KlicksPerPixel) && iObjCount < ctMaxObjsInField) { Objects[iObjCount++] = pObj; // See if the object should take damage if (pType->pDamageDesc) { CVector vDeltaV = pObj->GetVel() - GetVel(); int iSpeed = (int)(vDeltaV.Length() / g_KlicksPerPixel); if (iSpeed == 0) iSpeed = 1; if (mathRandom(1, 1000) < (iDensity * iSpeed)) { SDamageCtx Ctx; Ctx.pObj = pObj; Ctx.pDesc = pType->pDamageDesc; Ctx.Damage = pType->pDamageDesc->m_Damage; if (IsAutomatedWeapon()) Ctx.Damage.SetAutomatedWeapon(); Ctx.iDirection = VectorToPolar(vDeltaV); Ctx.vHitPos = pObj->GetPos(); Ctx.pCause = this; Ctx.Attacker = CDamageSource(this, killedByDamage); pObj->Damage(Ctx); } } } } } } // If we're computing drag then we need to compute the new velocity // of the whole particle system CVector vNewVel; if (pType->m_fDrag) vNewVel = GetVel() * g_SpaceDragFactor; // Iterate over all particles SParticle *pParticle = pGroup->pParticles; SParticle *pEnd = pParticle + pGroup->iCount; while (pParticle < pEnd) { if (pParticle->IsValid()) { // Lifespan. If we're far away and we're regenerating, // then don't bother to compute lifespan. if (pType->m_fLifespan && !(bFarAway && (pType->m_fRegenerate && pType->iRegenerationTimer))) { if (--pParticle->iLifeLeft == 0) { // Do we regenerate? if (pType->m_fRegenerate && pType->iRegenerationTimer) { pParticle->iLifeLeft = pType->iLifespan; pParticle->vPos = NullVector; // Speed Metric rSpeed = mathRandom(1, 100) * (pType->rAveSpeed / 100.0); if (pType->iDirection == -1) pParticle->vVel = PolarToVector(mathRandom(0, 359), rSpeed); else { int iAngle = (pType->iDirection + 360 + mathRandom(0, 2 * pType->iDirRange) - pType->iDirRange) % 360; pParticle->vVel = PolarToVector(iAngle, rSpeed); } } // Otherwise we die else { pParticle->iLifeLeft = -1; pGroup->iAlive--; pParticle++; continue; } } } // Update the position if (!bFarAway) { pParticle->vPos = pParticle->vPos + pParticle->vVel; // Change the velocity to keep the particles within // the radius if (pType->m_fMaxRadius) { Metric rDist2 = pParticle->vPos.Length2(); if (pType->m_fMaxRadius && rDist2 > rMaxDist2) { CVector vChange = pParticle->vPos + g_KlicksPerPixel * pParticle->vPos.Perpendicular().Normal(); pParticle->vVel = pParticle->vVel - (0.00005 * vChange); } else if (rDist2 < rMinDist2) { CVector vNormal = pParticle->vPos.Normal(); CVector vChange = g_KlicksPerPixel * (400 * vNormal - 50 * vNormal.Perpendicular()); pParticle->vVel = pParticle->vVel + (0.00005 * vChange); } else pParticle->vVel = pParticle->vVel * pType->rDampening; } if (pType->m_fDrag) { // Compute the new absolute velocity (after drag) CVector vAbsolute = pType->rDampening * (pParticle->vVel + GetVel()); // The particle velocity is the absolute vel minus the // system velocity. pParticle->vVel = vAbsolute - vNewVel; } // Change the velocity based on influences from other objects if (pType->m_fWake && (iTick % 4) == 0) { for (int i = 0; i < iObjCount; i++) { Metric rDist2 = (Objects[i]->GetPos() - (pParticle->vPos + GetPos())).Length2(); if (rDist2 < g_KlicksPerPixel * g_KlicksPerPixel * 1000) { if (Objects[i]->GetVel().Dot(pParticle->vVel) < Objects[i]->GetVel().Length2()) pParticle->vVel = pParticle->vVel + 0.2 * Objects[i]->GetVel(); } } } } } pParticle++; } // Regeneration timer if (pType->m_fRegenerate && pType->iRegenerationTimer) pType->iRegenerationTimer--; // If there are no more particles left alive in this group then kill // the group if (pGroup->iAlive == 0) { SParticleArray *pNext = pGroup->pNext; SParticleArray *pPrev = NULL; // Find the previous group SParticleArray *pFind = m_pFirstGroup; while (pFind != pGroup) { if (pPrev) pPrev = pPrev->pNext; else pPrev = m_pFirstGroup; pFind = pFind->pNext; } // Fix up the linked list if (pPrev) pPrev->pNext = pNext; else m_pFirstGroup = pNext; // Delete the group delete pGroup; pGroup = pNext; } // Otherwise, next group else pGroup = pGroup->pNext; } // If we have no more groups then we destroy ourselves if (m_pFirstGroup == NULL) { Destroy(removedFromSystem, CDamageSource()); return; } // If we're moving, slow down SetVel(CVector(GetVel().GetX() * g_SpaceDragFactor, GetVel().GetY() * g_SpaceDragFactor)); }