int CArmorClass::FireGetMaxHP (CItemCtx &ItemCtx, int iMaxHP) const // FireGetMaxHP // // Fire GetMaxHP event { SEventHandlerDesc Event; if (FindEventHandlerArmorClass(evtGetMaxHP, &Event)) { // Setup arguments CCodeChainCtx Ctx; Ctx.SaveAndDefineSourceVar(ItemCtx.GetSource()); Ctx.SaveAndDefineItemVar(ItemCtx); Ctx.DefineInteger(CONSTLIT("aMaxHP"), iMaxHP); ICCItem *pResult = Ctx.Run(Event); if (pResult->IsError()) ItemCtx.GetSource()->ReportEventError(GET_MAX_HP_EVENT, pResult); else if (!pResult->IsNil()) iMaxHP = Max(0, pResult->GetIntegerValue()); Ctx.Discard(pResult); } return iMaxHP; }
void CShieldClass::CalcMinMaxHP (CItemCtx &Ctx, int iCharges, int iArmorSegs, int iTotalHP, int *retiMin, int *retiMax) const // CalcMinMaxHP // // Returns the min and max HP of this shield // // iCharges = m_iMaxCharges or the current charges on item // iArmorSegs = count of armor segments on ship (or 0) // iTotalHP = current total HP of all armor segments (or 0) { int iMax = m_iHitPoints; int iMin = iMax; if (m_iExtraHPPerCharge) iMax = Max(0, iMax + (m_iExtraHPPerCharge * iCharges)); if (m_iArmorShield) { iMin = m_iArmorShield; if (iArmorSegs > 0) iMax = Min(iMax, ((m_iArmorShield * iTotalHP / iArmorSegs) + 5) / 10); } // If we're installed, fire the custom event to get max HPs if (Ctx.GetSource() && Ctx.GetDevice()) { CItemEnhancementStack *pEnhancements = Ctx.GetDevice()->GetEnhancements(); iMax = FireGetMaxHP(Ctx.GetDevice(), Ctx.GetSource(), iMax); // Apply bonus from device (which includes mods) if (pEnhancements) iMax += (iMax * pEnhancements->GetBonus() / 100); } // Otherwise, we just apply mods else { const CItemEnhancement &Mods = Ctx.GetMods(); if (Mods.IsNotEmpty()) iMax = iMax * Mods.GetHPAdj() / 100; } // Done if (iMin > iMax) iMin = iMax; if (retiMin) *retiMin = iMin; if (retiMax) *retiMax = iMax; }
bool CDeviceClass::AccumulateEnhancements (CItemCtx &Device, CInstalledDevice *pTarget, TArray<CString> &EnhancementIDs, CItemEnhancementStack *pEnhancements) // AccumulateEnhancements // // If this device can enhance pTarget, then we add to the list of enhancements. { int i; bool bEnhanced = false; CInstalledDevice *pDevice = Device.GetDevice(); CSpaceObject *pSource = Device.GetSource(); // See if we can enhance the target device if (pDevice == NULL || (pDevice->IsEnabled() && !pDevice->IsDamaged())) { for (i = 0; i < m_Enhancements.GetCount(); i++) { // If this type of enhancement has already been applied, skip it if (!m_Enhancements[i].sType.IsBlank() && EnhancementIDs.Find(m_Enhancements[i].sType)) continue; // If we don't match the criteria, skip it. if (pSource && pTarget && !pSource->GetItemForDevice(pTarget).MatchesCriteria(m_Enhancements[i].Criteria)) continue; // Add the enhancement pEnhancements->Insert(m_Enhancements[i].Enhancement); bEnhanced = true; // Remember that we added this enhancement class if (!m_Enhancements[i].sType.IsBlank()) EnhancementIDs.Insert(m_Enhancements[i].sType); } } // Let sub-classes add their own if (OnAccumulateEnhancements(Device, pTarget, EnhancementIDs, pEnhancements)) bEnhanced = true; // Done return bEnhanced; }
void CArmorClass::FireOnArmorDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx) // FireOnArmorDamage // // Fires OnArmorDamage event { SEventHandlerDesc Event; if (FindEventHandlerArmorClass(evtOnArmorDamage, &Event)) { // Setup arguments CCodeChainCtx CCCtx; CCCtx.SaveAndDefineSourceVar(ItemCtx.GetSource()); CCCtx.SaveAndDefineItemVar(ItemCtx); CCCtx.DefineInteger(CONSTLIT("aArmorHP"), Ctx.iHPLeft); CCCtx.DefineInteger(CONSTLIT("aArmorSeg"), Ctx.iSectHit); CCCtx.DefineSpaceObject(CONSTLIT("aAttacker"), Ctx.Attacker.GetObj()); CCCtx.DefineSpaceObject(CONSTLIT("aCause"), Ctx.pCause); CCCtx.DefineDamageEffects(CONSTLIT("aDamageEffects"), Ctx); CCCtx.DefineInteger(CONSTLIT("aDamageHP"), Ctx.iDamage); CCCtx.DefineString(CONSTLIT("aDamageType"), GetDamageShortName(Ctx.Damage.GetDamageType())); CCCtx.DefineInteger(CONSTLIT("aHitDir"), Ctx.iDirection); CCCtx.DefineVector(CONSTLIT("aHitPos"), Ctx.vHitPos); CCCtx.DefineSpaceObject(CONSTLIT("aOrderGiver"), (Ctx.Attacker.GetObj() ? Ctx.Attacker.GetObj()->GetOrderGiver(Ctx.Attacker.GetCause()) : NULL)); CCCtx.DefineItemType(CONSTLIT("aWeaponType"), Ctx.pDesc->GetWeaponType()); ICCItem *pResult = CCCtx.Run(Event); // If we return Nil, then nothing if (pResult->IsNil()) NULL; // If we return an integer, then this is the damage that armor should take else if (pResult->IsInteger()) Ctx.iDamage = pResult->GetIntegerValue(); // If we return a list, then we it to be a DamageEffects list (modifications to // aDamageEffects) else if (pResult->IsList()) LoadDamageEffectsFromItem(pResult, Ctx); CCCtx.Discard(pResult); } }
int CItemEnhancementStack::CalcActivateDelay (CItemCtx &DeviceCtx) const // CalcActivateDelay // // Calculates the activation delay (in ticks) for the given device if we apply // this enhancement stack. { int i; CInstalledDevice *pDevice = DeviceCtx.GetDevice(); if (pDevice == NULL) return 0; Metric rDelay = -1.0; for (i = 0; i < m_Stack.GetCount(); i++) { int iMin, iMax; int iAdj = m_Stack[i].GetActivateRateAdj(&iMin, &iMax); if (iAdj != 100) { if (rDelay < 0.0) { pDevice->SetActivateDelayAdj(100); rDelay = pDevice->GetActivateDelay(DeviceCtx.GetSource()); } rDelay = iAdj * rDelay / 100.0; if (rDelay < (Metric)iMin) rDelay = (Metric)iMin; else if (iMax > 0 && rDelay > (Metric)iMax) rDelay = (Metric)iMax; } } return (rDelay < 0.0 ? 100 : (int)(rDelay + 0.5)); }
int CItemEnhancementStack::CalcActivateDelay (CItemCtx &DeviceCtx) const // CalcActivateDelay // // Calculates the activation delay (in ticks) for the given device if we apply // this enhancement stack. { int i; CInstalledDevice *pDevice = DeviceCtx.GetDevice(); if (pDevice == NULL) return 0; // Get the raw activation delay. NOTE: This DOES NOT include // any enhancements on the item. Metric rDelay = pDevice->GetClass()->GetActivateDelay(pDevice, DeviceCtx.GetSource()); // Apply enhancements (including on the item itself) for (i = 0; i < m_Stack.GetCount(); i++) { int iMin, iMax; int iAdj = m_Stack[i].GetActivateRateAdj(&iMin, &iMax); if (iAdj != 100) { rDelay = iAdj * rDelay / 100.0; if (rDelay < (Metric)iMin) rDelay = (Metric)iMin; else if (iMax > 0 && rDelay > (Metric)iMax) rDelay = (Metric)iMax; } } return (int)(rDelay + 0.5); }
void CArmorClass::CalcDamageEffects (CItemCtx &ItemCtx, SDamageCtx &Ctx) // CalcDamageEffects // // Initialize the damage effects based on the damage and on this armor type. { CSpaceObject *pSource = ItemCtx.GetSource(); CInstalledArmor *pArmor = ItemCtx.GetArmor(); // Compute all the effects (if we don't have installed armor, then the // caller is responsible for setting this). if (pArmor) Ctx.iHPLeft = pArmor->GetHitPoints(); // Reflect Ctx.bReflect = (IsReflective(ItemCtx, Ctx.Damage) && Ctx.iDamage > 0); // Disintegration int iDisintegration = Ctx.Damage.GetDisintegrationDamage(); Ctx.bDisintegrate = (iDisintegration > 0 && !IsDisintegrationImmune(ItemCtx)); // Shatter int iShatter = Ctx.Damage.GetShatterDamage(); if (iShatter) { // Compute the threshold mass. Below this size, we shatter the object int iMassLimit = 10 * mathPower(5, iShatter); Ctx.bShatter = (pSource && pSource->GetMass() < iMassLimit); } else Ctx.bShatter = false; // Blinding int iBlinding = Ctx.Damage.GetBlindingDamage(); if (iBlinding && !IsBlindingDamageImmune(ItemCtx)) { // The chance of being blinded is dependent // on the rating. int iChance = 4 * iBlinding * iBlinding * GetBlindingDamageAdj() / 100; Ctx.bBlind = (mathRandom(1, 100) <= iChance); Ctx.iBlindTime = Ctx.iDamage * g_TicksPerSecond / 2; } else Ctx.bBlind = false; // EMP int iEMP = Ctx.Damage.GetEMPDamage(); if (iEMP && !IsEMPDamageImmune(ItemCtx)) { // The chance of being paralyzed is dependent // on the EMP rating. int iChance = 4 * iEMP * iEMP * GetEMPDamageAdj() / 100; Ctx.bParalyze = (mathRandom(1, 100) <= iChance); Ctx.iParalyzeTime = Ctx.iDamage * g_TicksPerSecond / 2; } else Ctx.bParalyze = false; // Device disrupt int iDeviceDisrupt = Ctx.Damage.GetDeviceDisruptDamage(); if (iDeviceDisrupt && !IsDeviceDamageImmune(ItemCtx)) { // The chance of damaging a device depends on the rating. int iChance = 4 * iDeviceDisrupt * iDeviceDisrupt * GetDeviceDamageAdj() / 100; Ctx.bDeviceDisrupt = (mathRandom(1, 100) <= iChance); Ctx.iDisruptTime = 2 * Ctx.iDamage * g_TicksPerSecond; } else Ctx.bDeviceDisrupt = false; // Device damage int iDeviceDamage = Ctx.Damage.GetDeviceDamage(); if (iDeviceDamage && !IsDeviceDamageImmune(ItemCtx)) { // The chance of damaging a device depends on the rating. int iChance = 4 * iDeviceDamage * iDeviceDamage * GetDeviceDamageAdj() / 100; Ctx.bDeviceDamage = (mathRandom(1, 100) <= iChance); } else Ctx.bDeviceDamage = false; // Radiation int iRadioactive = Ctx.Damage.GetRadiationDamage(); Ctx.bRadioactive = (iRadioactive > 0 && !IsRadiationImmune(ItemCtx)); // Some effects decrease damage if (iBlinding || iEMP) Ctx.iDamage = 0; else if (iDeviceDamage) Ctx.iDamage = Ctx.iDamage / 2; }
EDamageResults CArmorClass::AbsorbDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx) // AbsorbDamage // // Handles getting hit by damage. // // Returns damageNoDamage if all the damage was absorbed and no further processing is necessary // Returns damageDestroyed if the source was destroyed // Returns damageArmorHit if source was damage and further processing (destroy check) is needed // // Sets Ctx.iDamage to the amount of hit points left after damage absorption. { CSpaceObject *pSource = ItemCtx.GetSource(); CInstalledArmor *pArmor = ItemCtx.GetArmor(); if (pSource == NULL || pArmor == NULL) return damageNoDamage; // Compute all the effects (this initializes elements in Ctx). CalcDamageEffects(ItemCtx, Ctx); // First give custom weapons a chance bool bCustomDamage = Ctx.pDesc->FireOnDamageArmor(Ctx); if (pSource->IsDestroyed()) return damageDestroyed; // Damage adjustment CalcAdjustedDamage(ItemCtx, Ctx); // If the armor has custom code to deal with damage, handle it here. FireOnArmorDamage(ItemCtx, Ctx); if (pSource->IsDestroyed()) return damageDestroyed; // If this armor section reflects this kind of damage then // send the damage on if (Ctx.bReflect) { if (Ctx.pCause) Ctx.pCause->CreateReflection(Ctx.vHitPos, (Ctx.iDirection + 120 + mathRandom(0, 120)) % 360); return damageNoDamage; } // If this is a disintegration attack, then disintegrate the ship if (Ctx.bDisintegrate) { if (!pSource->OnDestroyCheck(killedByDisintegration, Ctx.Attacker)) return damageNoDamage; pSource->Destroy(killedByDisintegration, Ctx.Attacker); return damageDestroyed; } // If this is a shatter attack, see if the ship is destroyed if (Ctx.bShatter) { if (!pSource->OnDestroyCheck(killedByShatter, Ctx.Attacker)) return damageNoDamage; pSource->Destroy(killedByShatter, Ctx.Attacker); return damageDestroyed; } // If this is a paralysis attack and we've gotten past the shields // then freeze the ship. if (Ctx.bParalyze) pSource->MakeParalyzed(Ctx.iParalyzeTime); // If this is blinding damage then our sensors are disabled if (Ctx.bBlind) pSource->MakeBlind(Ctx.iBlindTime); // If this attack is radioactive, then contaminate the ship if (Ctx.bRadioactive) pSource->OnHitByRadioactiveDamage(Ctx); // If this is device damage, then see if any device is damaged if (Ctx.bDeviceDamage) pSource->OnHitByDeviceDamage(); if (Ctx.bDeviceDisrupt) pSource->OnHitByDeviceDisruptDamage(Ctx.iDisruptTime); // Create a hit effect. (Many weapons show an effect even if no damage was // done.) Ctx.pDesc->CreateHitEffect(pSource->GetSystem(), Ctx); // If no damage has reached us, then we're done if (Ctx.iDamage == 0 && !bCustomDamage) return damageNoDamage; // Give source events a chance to change the damage before we // subtract from armor. if (pSource->HasOnDamageEvent()) { pSource->FireOnDamage(Ctx); if (pSource->IsDestroyed()) return damageDestroyed; } // Take damage if (Ctx.iDamage <= pArmor->GetHitPoints()) { pArmor->IncHitPoints(-Ctx.iDamage); Ctx.iDamage = 0; } else { Ctx.iDamage -= pArmor->GetHitPoints(); pArmor->SetHitPoints(0); } return damageArmorHit; }
void CShieldClass::FireOnShieldDamage (CItemCtx &ItemCtx, SDamageCtx &Ctx) // FireOnShieldDamage // // Fire OnShieldDamage { SEventHandlerDesc Event; if (FindEventHandlerShieldClass(evtOnShieldDamage, &Event)) { // Setup arguments CCodeChainCtx CCCtx; CCCtx.SaveAndDefineSourceVar(ItemCtx.GetSource()); CCCtx.SaveAndDefineItemVar(ItemCtx); CCCtx.DefineInteger(CONSTLIT("aArmorSeg"), Ctx.iSectHit); CCCtx.DefineSpaceObject(CONSTLIT("aCause"), Ctx.pCause); CCCtx.DefineSpaceObject(CONSTLIT("aAttacker"), Ctx.Attacker.GetObj()); CCCtx.DefineSpaceObject(CONSTLIT("aOrderGiver"), (Ctx.Attacker.GetObj() ? Ctx.Attacker.GetObj()->GetOrderGiver(Ctx.Attacker.GetCause()) : NULL)); CCCtx.DefineVector(CONSTLIT("aHitPos"), Ctx.vHitPos); CCCtx.DefineInteger(CONSTLIT("aHitDir"), Ctx.iDirection); CCCtx.DefineInteger(CONSTLIT("aDamageHP"), Ctx.iDamage); CCCtx.DefineString(CONSTLIT("aDamageType"), GetDamageShortName(Ctx.Damage.GetDamageType())); CCCtx.DefineItemType(CONSTLIT("aWeaponType"), Ctx.pDesc->GetWeaponType()); CCCtx.DefineInteger(CONSTLIT("aShieldHP"), Ctx.iHPLeft); CCCtx.DefineInteger(CONSTLIT("aShieldDamageHP"), Ctx.iShieldDamage); CCCtx.DefineInteger(CONSTLIT("aArmorDamageHP"), Ctx.iDamage - Ctx.iAbsorb); if (Ctx.bReflect) { CCCtx.DefineString(CONSTLIT("aShieldReflect"), STR_SHIELD_REFLECT); CCCtx.DefineInteger(CONSTLIT("aOriginalShieldDamageHP"), Ctx.iOriginalShieldDamage); CCCtx.DefineInteger(CONSTLIT("aOriginalArmorDamageHP"), Ctx.iDamage - Ctx.iOriginalAbsorb); } else { CCCtx.DefineNil(CONSTLIT("aShieldReflect")); CCCtx.DefineInteger(CONSTLIT("aOriginalShieldDamageHP"), Ctx.iShieldDamage); CCCtx.DefineInteger(CONSTLIT("aOriginalArmorDamageHP"), Ctx.iDamage - Ctx.iAbsorb); } ICCItem *pResult = CCCtx.Run(Event); // If we return Nil, then nothing if (pResult->IsNil()) NULL; // If an error, report it else if (pResult->IsError()) ItemCtx.GetSource()->ReportEventError(ON_SHIELD_DAMAGE_EVENT, pResult); // If we return a list, then modify variables else if (pResult->IsList()) { // A single value means we modified the damage to armor if (pResult->GetCount() == 1) { if (strEquals(pResult->GetElement(0)->GetStringValue(), STR_SHIELD_REFLECT)) { Ctx.bReflect = true; Ctx.iAbsorb = Ctx.iDamage; Ctx.iShieldDamage = 0; } else { Ctx.iShieldDamage = Max(0, Min(pResult->GetElement(0)->GetIntegerValue(), Ctx.iHPLeft)); if (Ctx.bReflect) { Ctx.bReflect = false; Ctx.iAbsorb = Ctx.iOriginalAbsorb; } } } // Two values mean we modified both damage to armor and shield damage else if (pResult->GetCount() == 2) { Ctx.bReflect = false; Ctx.iShieldDamage = Max(0, Min(pResult->GetElement(0)->GetIntegerValue(), Ctx.iHPLeft)); Ctx.iAbsorb = Max(0, Ctx.iDamage - Max(0, pResult->GetElement(1)->GetIntegerValue())); } // Otherwise, we deal with reflection else { Ctx.bReflect = strEquals(pResult->GetElement(0)->GetStringValue(), STR_SHIELD_REFLECT); Ctx.iShieldDamage = Max(0, Min(pResult->GetElement(1)->GetIntegerValue(), Ctx.iHPLeft)); Ctx.iAbsorb = Max(0, Ctx.iDamage - Max(0, pResult->GetElement(2)->GetIntegerValue())); } } CCCtx.Discard(pResult); } }