diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 721364d064..ff73f37bb6 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -77,8 +77,13 @@ Promote.IncludeSpawns=no ; boolean *Buildings, Infantries and Vehicles with Shield in [Fantasy ADVENTURE](https://www.moddb.com/mods/fantasy-adventure)* - Now you can have a shield for any TechnoType if `Shield.Strength` is set greater than 0. It serves as a second health pool with independent `Armor` and `Strength` values. + - Negative damage will recover shield, unless shield has been broken. If shield isn't full, all negative damage will be absorbed by shield. + - When the TechnoType with a unbroken shield, `Shield.Armor` will replace `Armor` for game calculation. +- When executing `DeploysInto` or `UndeploysInto`, if both of the TechnoClasses have shields, the transformed unit/building would keep relative shield health (in percents), same as with `Strength`. If one of the TechnoTypes doesn't have shields, it's shield's state on conversion will be preserved until converted back. + - This also works with Ares' `Convert.*`. - `Shield.AbsorbOverDamage`controls whether or not the shield absorbs damage dealt beyond shield's current strength when the shield breaks. - `Shield.SelfHealing` and `Shield.Respawn` respect the following settings: 0.0 disables the feature, 1%-100% recovers/respawns the shield strength in percentage, other number recovers/respawns the shield strength directly. Specially, `Shield.SelfHealing` with a negative number deducts the shield strength. + - If you want shield recovers/respawns 1 HP per time, currently you need to set tag value to any number between 1 and 2, like `1.1`. - `Shield.SelfHealing.Rate` and `Shield.Respawn.Rate` respect the following settings: 0.0 instantly recovers the shield, other values determine the frequency of shield recovers/respawns in ingame minutes. - `Shield.IdleAnim`, if set, will be played while the shield is intact. This animation is automatically set to loop indefinitely. - `Shield.BreakAnim`, if set, will be played when the shield has been broken. @@ -88,7 +93,7 @@ Promote.IncludeSpawns=no ; boolean - `Pips.Shield` can be used to specify which pip frame should be used as shield strength. If only 1 digit set, then it will always display it, or if 3 digits set, it will respect `ConditionYellow` and `ConditionRed`. `Pips.Shield.Building` is used for BuildingTypes. - `pipbrd.shp` will use its 4th frame to display an infantry's shield strength and the 3th frame for other units if `pipbrd.shp` has extra 2 frames. And `Shield.BracketDelta` can be used as additonal `PixelSelectionBracketDelta` for shield strength. - Warheads have new options that interact with shields. - - `PenetratesShield` allows the warhead ignore the shield and always deal full damage to the TechnoType itself. It also allows targeting the TechnoType even if weapons using the warhead cannot target or damage the shield. + - `PenetratesShield` allows the warhead ignore the shield and always deal full damage to the TechnoType itself. It also allows targeting the TechnoType as if shield isn't existed. - `BreaksShield` allows the warhead to always break shields of TechnoTypes, regardless of the amount of strength the shield has remaining or the damage dealt, assuming it affects the shield's armor type. Residual damage, if there is any, still respects `Shield.AbsorbOverDamage`. In `rulesmd.ini`: diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 9694b17e8e..8ab3b020aa 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -2,59 +2,65 @@ #include #include -#include "../_Container.hpp" -#include "../../Utilities/TemplateDef.h" +#include +#include -#include "../../Misc/Shield.h" +#include class BulletClass; class TechnoExt { public: - using base_type = TechnoClass; + using base_type = TechnoClass; - class ExtData final : public Extension - { - public: - Valueable InterceptedBullet; - std::unique_ptr ShieldData; + class ExtData final : public Extension + { + public: + Valueable InterceptedBullet; + std::unique_ptr ShieldData; - ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject), - InterceptedBullet(nullptr), - ShieldData() - { } + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) + ,InterceptedBullet(nullptr) + ,ShieldData() + { } - virtual ~ExtData() = default; - virtual void InvalidatePointer(void* ptr, bool bRemoved) override {} - virtual void LoadFromStream(PhobosStreamReader& Stm) override; - virtual void SaveToStream(PhobosStreamWriter& Stm) override; - private: - template - void Serialize(T& Stm); - }; + virtual ~ExtData() = default; - class ExtContainer final : public Container { - public: - ExtContainer(); - ~ExtContainer(); + virtual void InvalidatePointer(void* ptr, bool bRemoved) override + { + this->ShieldData->InvalidatePointer(ptr); + } - virtual void InvalidatePointer(void* ptr, bool bRemoved) override; - }; + virtual void LoadFromStream(PhobosStreamReader& Stm) override; + virtual void SaveToStream(PhobosStreamWriter& Stm) override; - static ExtContainer ExtMap; + private: + template + void Serialize(T& Stm); + }; - static bool LoadGlobals(PhobosStreamReader& Stm); - static bool SaveGlobals(PhobosStreamWriter& Stm); + class ExtContainer final : public Container + { + public: + ExtContainer(); + ~ExtContainer(); - static bool IsHarvesting(TechnoClass* pThis); - static bool HasAvailableDock(TechnoClass* pThis); + virtual void InvalidatePointer(void* ptr, bool bRemoved) override; + }; - static void TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClass* pTechnoTo); + static ExtContainer ExtMap; - static void ApplyMindControlRangeLimit(TechnoClass* pThis); - static void ApplyInterceptor(TechnoClass* pThis); - static void ApplyPowered_KillSpawns(TechnoClass* pThis); - static void ApplySpawn_LimitRange(TechnoClass* pThis); + static bool LoadGlobals(PhobosStreamReader& Stm); + static bool SaveGlobals(PhobosStreamWriter& Stm); + static bool IsHarvesting(TechnoClass* pThis); + static bool HasAvailableDock(TechnoClass* pThis); + + static void TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClass* pTechnoTo); + + static void ApplyMindControlRangeLimit(TechnoClass* pThis); + static void ApplyInterceptor(TechnoClass* pThis); + static void ApplyPowered_KillSpawns(TechnoClass* pThis); + static void ApplySpawn_LimitRange(TechnoClass* pThis); }; diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp index cc117bec2e..d1d733e6f5 100644 --- a/src/Ext/Techno/Hooks.Shield.cpp +++ b/src/Ext/Techno/Hooks.Shield.cpp @@ -1,7 +1,9 @@ #include "Body.h" #include +#include #include "../TechnoType/Body.h" +#include "../WarheadType/Body.h" // #issue 88 : shield logic DEFINE_HOOK(701900, TechnoClass_ReceiveDamage_Shield, 6) @@ -13,32 +15,112 @@ DEFINE_HOOK(701900, TechnoClass_ReceiveDamage_Shield, 6) //GET_STACK(HouseClass*, pSourceHouse, -0x1C); auto pExt = TechnoExt::ExtMap.Find(pThis); - if (auto pShieldData = pExt->ShieldData.get()) { - auto nDamageLeft = pShieldData->ReceiveDamage(args); + if (auto pShieldData = pExt->ShieldData.get()) + { + if (!pShieldData->Available()) + return 0; - if (nDamageLeft >= 0) { + auto nDamageLeft = pShieldData->ReceiveDamage(args); + if (nDamageLeft >= 0) *args->Damage = nDamageLeft; - } } return 0; } -DEFINE_HOOK(6FC339, TechnoClass_CanFire_Shield, 6) +DEFINE_HOOK_AGAIN(70CF39, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_EvalThreatRating_Shield +DEFINE_HOOK_AGAIN(6F7D31, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_CanAutoTargetObject_Shield +DEFINE_HOOK_AGAIN(6FCB64, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_CanFire_Shield +DEFINE_HOOK(708AEB, TechnoClass_ReplaceArmorWithShields, 6) //TechnoClass_ShouldRetaliate_Shield { - GET_STACK(TechnoClass*, pTarget, STACK_OFFS(0x20, -0x4)); - GET(TechnoClass*, pThis, ESI); - GET(WeaponTypeClass*, pWeapon, EDI); + WeaponTypeClass* pWeapon = nullptr; + if (R->Origin() == 0x708AEB) + pWeapon = R->ESI(); + else if (R->Origin() == 0x6F7D31) + pWeapon = R->EBP(); + else + pWeapon = R->EBX(); + + if (auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead)) + if (pWHExt->PenetratesShield) + return 0; + + TechnoClass* pTarget = nullptr; + if (R->Origin() == 0x6F7D31 || R->Origin() == 0x70CF39) + pTarget = R->ESI(); + else + pTarget = R->EBP(); + + if (auto pExt = TechnoExt::ExtMap.Find(pTarget)) + { + if (auto pShieldData = pExt->ShieldData.get()) + { + if (pShieldData->Available() && pShieldData->GetShieldHP()) + { + R->EAX(TechnoTypeExt::ExtMap.Find(pTarget->GetTechnoType())->Shield_Armor); + return R->Origin() + 6; + } + } + } - if (auto pExt = TechnoExt::ExtMap.Find(pTarget)) { - if (auto pShieldData = pExt->ShieldData.get()) { - if (!pShieldData->CanBeTargeted(pWeapon, pThis)) { - return 0x6FCB7E; + return 0; +} + +//Abandoned because of Ares!!!! - Uranusian +/* +DEFINE_HOOK_AGAIN(6F3725, TechnoClass_WhatWeaponShouldIUse_Shield, 6) +DEFINE_HOOK(6F36F2, TechnoClass_WhatWeaponShouldIUse_Shield, 6) +{ + GET(TechnoClass*, pTarget, EBP); + if (auto pExt = TechnoExt::ExtMap.Find(pTarget)) + { + if (auto pShieldData = pExt->ShieldData.get()) + { + if (pShieldData->GetShieldHP()) + { + auto pTypeExt = TechnoTypeExt::ExtMap.Find(pTarget->GetTechnoType()); + + if (R->Origin() == 0x6F36F2) + R->ECX(pTypeExt->Shield_Armor); + else + R->EAX(pTypeExt->Shield_Armor); + + return R->Origin() + 6; } } } return 0; } +*/ + +DEFINE_HOOK(6F36DB, TechnoClass_WhatWeaponShouldIUse_Shield, 8) +{ + GET(TechnoClass*, pThis, ESI); + GET(TechnoClass*, pTarget, EBP); + + if (!pTarget) + return 0x6F37AD; //Target invalid, skip. (test ebp,ebp jz loc_6F37AD) + + if (auto pExt = TechnoExt::ExtMap.Find(pTarget)) + { + if (auto pShieldData = pExt->ShieldData.get()) + { + if (pShieldData->Available() && pShieldData->GetShieldHP()) + { + if (pThis->GetWeapon(1)) + { + if (!pShieldData->CanBeTargeted(pThis->GetWeapon(0)->WeaponType/*, pThis*/)) + return 0x6F3745; //Primary cannot attack, always use Secondary + + return 0x6F3754; //Further check in vanilla function + } + + return 0x6F37AD; //Don't have Secondary, always use Primary + } + } + } + return 0x6F36E3; //Target doesn't have a shield, back +} DEFINE_HOOK(6F9E50, TechnoClass_AI_Shield, 5) { @@ -46,13 +128,12 @@ DEFINE_HOOK(6F9E50, TechnoClass_AI_Shield, 5) auto pExt = TechnoExt::ExtMap.Find(pThis); auto pTypeData = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); - if (pTypeData->Shield_Strength) { - if (!pExt->ShieldData) { - pExt->ShieldData = std::make_unique(pThis); - } + if (pTypeData->Shield_Strength && !pExt->ShieldData) + pExt->ShieldData = std::make_unique(pThis); + if (pExt->ShieldData) pExt->ShieldData->AI(); - } + return 0; } @@ -61,36 +142,58 @@ DEFINE_HOOK(6F6AC4, TechnoClass_Remove_Shield, 5) GET(TechnoClass*, pThis, ECX); auto pExt = TechnoExt::ExtMap.Find(pThis); - if (pExt->ShieldData) { + if (pExt->ShieldData && + !(pThis->WhatAmI() == AbstractType::Building && pThis->GetTechnoType()->UndeploysInto && pThis->CurrentMission == Mission::Selling)) + { pExt->ShieldData = nullptr; } return 0; } -DEFINE_HOOK(6F65D1, TechnoClass_DrawHealthBar_DrawBuildingShieldBar, 6) +DEFINE_HOOK_AGAIN(44A03C, DeploysInto_UndeploysInto_SyncShieldStatus, 6) //BuildingClass_Mi_Selling_SyncShieldStatus +DEFINE_HOOK(739956, DeploysInto_UndeploysInto_SyncShieldStatus, 6) //UnitClass_Deploy_SyncShieldStatus +{ + GET(TechnoClass*, pThis, EBP); + GET(TechnoClass*, pInto, EBX); + auto pThisExt = TechnoExt::ExtMap.Find(pThis); + auto pIntoTypeExt = TechnoTypeExt::ExtMap.Find(pInto->GetTechnoType()); + + if (pThisExt->ShieldData && pIntoTypeExt->Shield_Strength) + { + ShieldTechnoClass::SyncShieldToAnother(pThis, pInto); + } + + if (pThis->WhatAmI() == AbstractType::Building && pThisExt->ShieldData) + pThisExt->ShieldData = nullptr; + + return 0; +} + + +DEFINE_HOOK(6F65D1, TechnoClass_DrawHealthBar_DrawBuildingShieldBar, 6) { GET(TechnoClass*, pThis, ESI); GET(int, iLength, EBX); GET_STACK(Point2D*, pLocation, STACK_OFFS(0x4C, -0x4)); GET_STACK(RectangleStruct*, pBound, STACK_OFFS(0x4C, -0x8)); auto pExt = TechnoExt::ExtMap.Find(pThis); - - if (pExt->ShieldData) { + + if (pExt->ShieldData && pExt->ShieldData->Available()) pExt->ShieldData->DrawShieldBar(iLength, pLocation, pBound); - } return 0; } -DEFINE_HOOK(6F683C,TechnoClass_DrawHealthBar_DrawOtherShieldBar,7) +DEFINE_HOOK(6F683C, TechnoClass_DrawHealthBar_DrawOtherShieldBar, 7) { GET(TechnoClass*, pThis, ESI); GET_STACK(Point2D*, pLocation, STACK_OFFS(0x4C, -0x4)); GET_STACK(RectangleStruct*, pBound, STACK_OFFS(0x4C, -0x8)); auto pExt = TechnoExt::ExtMap.Find(pThis); - if (pExt->ShieldData) { + if (pExt->ShieldData && pExt->ShieldData->Available()) + { int iLength = pThis->WhatAmI() == AbstractType::Infantry ? 8 : 17; pExt->ShieldData->DrawShieldBar(iLength, pLocation, pBound); } diff --git a/src/Misc/Shield.cpp b/src/Misc/Shield.cpp index 4de6762c01..1030ed467d 100644 --- a/src/Misc/Shield.cpp +++ b/src/Misc/Shield.cpp @@ -5,27 +5,37 @@ #include #include +#include + #include #include #include -ShieldTechnoClass::ShieldTechnoClass() :Techno{ nullptr }, HP{ 0 }, Timer_Respawn{}, Timer_SelfHealing{}{}; +ShieldTechnoClass::ShieldTechnoClass() : + Techno { nullptr }, + HP { 0 }, + Timer_Respawn { }, + Timer_SelfHealing { } +{ } ShieldTechnoClass::ShieldTechnoClass(TechnoClass* pTechno) : - Techno{ pTechno }, - HP{ this->GetExt()->Shield_Strength }, - Timer_Respawn{}, - Timer_SelfHealing{}, - Image{ nullptr }, - HaveAnim{ true }, - Temporal{ false } + Techno { pTechno }, + Update { true }, + HP { this->GetExt()->Shield_Strength }, + Timer_Respawn { }, + Timer_SelfHealing { }, + Image { nullptr }, + HaveAnim { true }, + Temporal { false } //Broken{ false } { + sprintf_s(this->TechnoID, this->Techno->get_ID()); this->CreateAnim(); } + const TechnoTypeExt::ExtData* ShieldTechnoClass::GetExt() { - return TechnoTypeExt::ExtMap.Find(Techno->GetTechnoType()); + return TechnoTypeExt::ExtMap.Find(this->Techno->GetTechnoType()); } bool ShieldTechnoClass::Load(PhobosStreamReader& Stm, bool RegisterForChange) @@ -54,47 +64,83 @@ bool ShieldTechnoClass::Save(PhobosStreamWriter& Stm) const .Success(); } +void ShieldTechnoClass::SyncShieldToAnother(TechnoClass* pFrom, TechnoClass* pTo) +{ + auto pFromExt = TechnoExt::ExtMap.Find(pFrom); + auto pToExt = TechnoExt::ExtMap.Find(pTo); + auto pToTypeExt = TechnoTypeExt::ExtMap.Find(pTo->GetTechnoType()); + + pToExt->ShieldData = std::make_unique(pTo); + pToExt->ShieldData->HP = int(pFromExt->ShieldData->GetShieldRatio() * pToTypeExt->Shield_Strength); +} + int ShieldTechnoClass::ReceiveDamage(args_ReceiveDamage* args) { //UNREFERENCED_PARAMETER(pWH); auto pWHExt = WarheadTypeExt::ExtMap.Find(args->WH); - if (!this->HP || *args->Damage == 0 || this->Techno->IsIronCurtained()) { + if (!this->HP || *args->Damage == 0 || this->Techno->IsIronCurtained() || pWHExt->PenetratesShield) return *args->Damage; - } int nDamage = 0; - if (pWHExt && pWHExt->CanTargetHouse(args->SourceHouse, this->Techno) && !args->WH->Temporal) { - nDamage = MapClass::GetTotalDamage(*args->Damage, args->WH, this->GetExt()->Shield_Armor, args->DistanceToEpicenter); + if (pWHExt->CanTargetHouse(args->SourceHouse, this->Techno) && !args->WH->Temporal) + { + if (*args->Damage > 0) + nDamage = MapClass::GetTotalDamage(*args->Damage, args->WH, this->GetExt()->Shield_Armor, args->DistanceToEpicenter); + else + nDamage = -MapClass::GetTotalDamage(-*args->Damage, args->WH, this->GetExt()->Shield_Armor, args->DistanceToEpicenter); } - if (nDamage > 0) { - + if (nDamage > 0) + { this->Timer_SelfHealing.Start(int(this->GetExt()->Shield_SelfHealing_Rate * 900)); //when attacked, restart the timer this->ResponseAttack(); auto residueDamage = nDamage - this->HP; - if (residueDamage >= 0 || pWHExt -> BreaksShield) { + if (residueDamage >= 0 || pWHExt->BreaksShield) + { + + if (pWHExt->BreaksShield && residueDamage < 0) + residueDamage = 0; - if (pWHExt->BreaksShield && residueDamage < 0) { - residueDamage = 0; - } + residueDamage = int((double)residueDamage / + GeneralUtils::GetWarheadVersusArmor(args->WH, this->GetExt()->Shield_Armor)); //only absord percentage damage this->BreakShield(); - return pWHExt->PenetratesShield ? *args->Damage : this->GetExt()->Shield_AbsorbOverDamage ? 0 : residueDamage; + return this->GetExt()->Shield_AbsorbOverDamage ? 0 : residueDamage; } - else if (pWHExt->PenetratesShield) { - return *args->Damage; - } - else { + else + { this->WeaponNullifyAnim(); this->HP = -residueDamage; return 0; } } - else { - return pWHExt->PenetratesShield ? *args->Damage : nDamage; //might change in future + else if (!nDamage) + { + return 0; + } + + else + { + auto LostHP = this->GetExt()->Shield_Strength - this->HP; + if (!LostHP) + { + auto result = *args->Damage; + if (*args->Damage * GeneralUtils::GetWarheadVersusArmor(args->WH, + static_cast(this->Techno->GetTechnoType()->Armor)) > 0) + result = 0; + return result; + } + + auto RemainLostHP = LostHP + nDamage; + if (RemainLostHP < 0) + this->HP = this->GetExt()->Shield_Strength; + else + this->HP -= nDamage; + + return 0; } } @@ -102,51 +148,53 @@ void ShieldTechnoClass::ResponseAttack() { if (this->Techno->Owner != HouseClass::Player) return; - if (this->Techno->WhatAmI() == AbstractType::Building) { + + if (this->Techno->WhatAmI() == AbstractType::Building) + { auto pBld = abstract_cast(this->Techno); this->Techno->Owner->BuildingUnderAttack(pBld); } - else if (this->Techno->WhatAmI() == AbstractType::Unit) { + else if (this->Techno->WhatAmI() == AbstractType::Unit) + { auto pUnit = abstract_cast(this->Techno); - if (pUnit->Type->Harvester) { + if (pUnit->Type->Harvester) + { auto pPos = pUnit->GetDestination(pUnit); - if (RadarEventClass::Create(RadarEventType::HarvesterAttacked, { (short)pPos.X / 256,(short)pPos.Y / 256 })) { + if (RadarEventClass::Create(RadarEventType::HarvesterAttacked, { (short)pPos.X / 256,(short)pPos.Y / 256 })) VoxClass::Play("EVA_OreMinerUnderAttack"); - } } } } void ShieldTechnoClass::WeaponNullifyAnim() { - if (this->GetExt()->Shield_HitAnim.isset()) { + if (this->GetExt()->Shield_HitAnim.isset()) GameCreate(this->GetExt()->Shield_HitAnim, this->Techno->GetCoords()); - } } -bool ShieldTechnoClass::CanBeTargeted(WeaponTypeClass* pWeapon, TechnoClass* pSource) +bool ShieldTechnoClass::CanBeTargeted(WeaponTypeClass* pWeapon/*, TechnoClass* pSource*/) { auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); - UNREFERENCED_PARAMETER(pWHExt); - - if (pWHExt->PenetratesShield) { - return true; - } - - bool result = - ((MapClass::GetTotalDamage(pWeapon->Damage, pWeapon->Warhead, this->GetExt()->Shield_Armor, 0) != 0) && pWeapon->Damage) - || !pWeapon->Damage; // we could check how is a warhead vs shield's armor - + + if (pWHExt->PenetratesShield) + return true; + + bool result = GeneralUtils::GetWarheadVersusArmor(pWeapon->Warhead, this->GetExt()->Shield_Armor) != 0.0; + return this->HP ? result : true; } void ShieldTechnoClass::AI() { + this->ConvertCheck(); + + if (!this->Update) + return; + this->TemporalCheck(); - if (!this->Techno || this->Techno->InLimbo || this->Techno->IsImmobilized || this->Techno->Transporter) { + if (!this->Techno || this->Techno->InLimbo || this->Techno->IsImmobilized || this->Techno->Transporter) return; - } this->DrawShield(); this->RespawnShield(); @@ -155,15 +203,66 @@ void ShieldTechnoClass::AI() void ShieldTechnoClass::TemporalCheck() { - if (this->Techno->TemporalTargetingMe && !this->Temporal) { + if (this->Techno->TemporalTargetingMe && !this->Temporal) + { this->Temporal = true; - if (this->HP == 0) this->Timer_Respawn.Pause(); - else this->Timer_SelfHealing.Pause(); + if (this->HP == 0) + this->Timer_Respawn.Pause(); + else + this->Timer_SelfHealing.Pause(); } - else if (!this->Techno->TemporalTargetingMe && this->Temporal) { + else if (!this->Techno->TemporalTargetingMe && this->Temporal) + { this->Temporal = false; - if (this->HP == 0) this->Timer_Respawn.Resume(); - else this->Timer_SelfHealing.Resume(); + if + (this->HP == 0) this->Timer_Respawn.Resume(); + else + this->Timer_SelfHealing.Resume(); + } +} + +void ShieldTechnoClass::ConvertCheck() +{ + if (strcmp(this->TechnoID, this->Techno->get_ID())) + { + if (this->GetExt()->Shield_Strength) + { + if (this->Update) + { + auto pOrigin = TechnoTypeClass::Find(this->TechnoID); + auto pOriginExt = TechnoTypeExt::ExtMap.Find(pOrigin); + this->HP = int((double)this->HP / pOriginExt->Shield_Strength * this->GetExt()->Shield_Strength); + } + else + { + if (this->HP == 0) + this->Timer_Respawn.Resume(); + else + this->Timer_SelfHealing.Resume(); + + this->Update = true; + } + + sprintf_s(this->TechnoID, this->Techno->get_ID()); + + if (this->HaveAnim) + { + this->KillAnim(); + this->CreateAnim(); + } + } + else + { + sprintf_s(this->TechnoID, this->Techno->get_ID()); + + if (this->HP == 0) + this->Timer_Respawn.Pause(); + else + this->Timer_SelfHealing.Pause(); + + this->Update = false; + this->KillAnim(); + } } } @@ -177,7 +276,7 @@ void ShieldTechnoClass::SelfHealing() { this->Timer_SelfHealing.Start(int(this->GetExt()->Shield_SelfHealing_Rate * 900)); this->HP += nSelfHealingAmount; - if (this->HP > this->GetExt()->Shield_Strength) + if (this->HP > this->GetExt()->Shield_Strength) { this->HP = this->GetExt()->Shield_Strength; this->Timer_SelfHealing.Stop(); @@ -189,9 +288,8 @@ int ShieldTechnoClass::GetPercentageAmount(double iStatus) { if (iStatus) { - if (iStatus >= -1.0 && iStatus <= 1.0) { + if (iStatus >= -1.0 && iStatus <= 1.0) return int(this->GetExt()->Shield_Strength * iStatus); - } if (iStatus < 0) { @@ -207,32 +305,35 @@ int ShieldTechnoClass::GetPercentageAmount(double iStatus) return 0; } -void ShieldTechnoClass::InvalidatePointer(void* ptr) { - if (this->Techno == ptr) { +void ShieldTechnoClass::InvalidatePointer(void* ptr) +{ + if (this->Techno == ptr) this->Techno = nullptr; - } - if (this->Image == ptr) { + if (this->Image == ptr) this->KillAnim(); - } } -void ShieldTechnoClass::UninitAnim::operator() (AnimClass* const pAnim) const { - auto buffer = abstract_cast(pAnim->OwnerObject); - pAnim->SetOwnerObject(nullptr); +void ShieldTechnoClass::UninitAnim::operator() (AnimClass* const pAnim) const +{ + TechnoClass* buffer = nullptr; - if (buffer) { - pAnim->UnInit(); + if (pAnim) + { + buffer = abstract_cast(pAnim->OwnerObject); + pAnim->SetOwnerObject(nullptr); } + + if (buffer) + pAnim->UnInit(); } void ShieldTechnoClass::BreakShield() { this->HP = 0; - if (this->GetExt()->Shield_Respawn > 0) { + if (this->GetExt()->Shield_Respawn > 0) this->Timer_Respawn.Start(int(this->GetExt()->Shield_Respawn_Rate * 900)); - } this->Timer_SelfHealing.Stop(); @@ -242,11 +343,8 @@ void ShieldTechnoClass::BreakShield() { if (auto pAnimType = this->GetExt()->Shield_BreakAnim) { - auto pAnim = GameCreate(pAnimType, this->Techno->GetCoords()); - - if (pAnim) { + if (auto pAnim = GameCreate(pAnimType, this->Techno->GetCoords())) pAnim->SetOwnerObject(this->Techno); - } } } } @@ -276,14 +374,16 @@ void ShieldTechnoClass::DrawShield() if (this->Techno->CloakState != CloakState::Uncloaked || this->HP < 1) { - if (this->HaveAnim) { + if (this->HaveAnim) + { this->KillAnim(); this->HaveAnim = false; } } - else + else { - if (!this->HaveAnim) { + if (!this->HaveAnim) + { this->CreateAnim(); this->HaveAnim = true; } @@ -293,13 +393,15 @@ void ShieldTechnoClass::DrawShield() void ShieldTechnoClass::CreateAnim() { if (this->Techno->CloakState != CloakState::Uncloaked) - { return; - } - if (this->GetExt()->Shield_IdleAnim.isset()) { - if (AnimTypeClass* const pAnimType = this->GetExt()->Shield_IdleAnim) { + + if (this->GetExt()->Shield_IdleAnim.isset()) + { + if (AnimTypeClass* const pAnimType = this->GetExt()->Shield_IdleAnim) + { this->Image.reset(GameCreate(pAnimType, this->Techno->Location)); - if (AnimClass* const pAnim = this->Image) { + if (AnimClass* const pAnim = this->Image) + { pAnim->SetOwnerObject(this->Techno); pAnim->RemainingIterations = 0xFFu; pAnim->Owner = this->Techno->Owner; @@ -308,14 +410,15 @@ void ShieldTechnoClass::CreateAnim() } } -void ShieldTechnoClass::KillAnim() +void ShieldTechnoClass::KillAnim() { this->Image.clear(); } void ShieldTechnoClass::DrawShieldBar(int iLength, Point2D* pLocation, RectangleStruct* pBound) { - if (this->HP > 0 || this->GetExt()->Shield_Respawn) { + if (this->HP > 0 || this->GetExt()->Shield_Respawn) + { if (this->Techno->WhatAmI() == AbstractType::Building) this->DrawShieldBarBuilding(iLength, pLocation, pBound); else @@ -323,93 +426,120 @@ void ShieldTechnoClass::DrawShieldBar(int iLength, Point2D* pLocation, Rectangle } } -void ShieldTechnoClass::DrawShieldBarBuilding(int iLength, Point2D* pLocation, RectangleStruct* pBound) { - int iCurrent = int((double)this->HP / this->GetExt()->Shield_Strength * iLength); - int iTotal = iCurrent; - if (iCurrent < 0) { - iCurrent = 0; - iTotal = 0; - } - if (iCurrent > iLength) { - iCurrent = iLength; - iTotal = iLength; - } - int frame = this->DrawShieldBar_Pip(); - Point2D vPos = { 0,0 }; - CoordStruct vCoords = { 0,0,0 }; +void ShieldTechnoClass::DrawShieldBarBuilding(int iLength, Point2D* pLocation, RectangleStruct* pBound) +{ + CoordStruct vCoords = { 0, 0, 0 }; this->Techno->GetTechnoType()->Dimension2(&vCoords); - Point2D vPos2 = { 0,0 }; + + Point2D vPos2 = { 0, 0 }; CoordStruct vCoords2 = { -vCoords.X / 2, vCoords.Y / 2,vCoords.Z }; TacticalClass::Instance->CoordsToScreen(&vPos2, &vCoords2); + Point2D vLoc = *pLocation; vLoc.X -= 5; vLoc.Y -= 3; - if (iCurrent > 0) { - int deltaX = 0; - int deltaY = 0; - int frameIdx = iTotal; - for (; frameIdx; frameIdx--) { + + Point2D vPos = { 0, 0 }; + + int iCurrent = int(this->GetShieldRatio() * iLength); + int min = this->HP != 0; + + if (iCurrent < min) + iCurrent = min; + if (iCurrent > iLength) + iCurrent = iLength; + + int iTotal = iCurrent; + int frame = this->DrawShieldBar_Pip(); + + if (iCurrent > 0) + { + int frameIdx, deltaX, deltaY; + for (frameIdx = iTotal, deltaX = 0, deltaY = 0; + frameIdx; + frameIdx--, deltaX += 4, deltaY -= 2) + { vPos.X = vPos2.X + vLoc.X + 4 * iLength + 3 - deltaX; vPos.Y = vPos2.Y + vLoc.Y - 2 * iLength + 4 - deltaY; - DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, frame, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); - deltaX += 4; - deltaY -= 2; + + DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, + frame, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); } + iCurrent = iTotal; } - if (iCurrent < iLength) { - int deltaX = 4 * iTotal; - int deltaY = -2 * iCurrent; - int frameIdx = iLength - iTotal; - for (; frameIdx; frameIdx--) { + + if (iCurrent < iLength) + { + int frameIdx, deltaX, deltaY; + for (frameIdx = iLength - iTotal, deltaX = 4 * iTotal, deltaY = -2 * iCurrent; + frameIdx; + frameIdx--, deltaX += 4, deltaY -= 2) + { vPos.X = vPos2.X + vLoc.X + 4 * iLength + 3 - deltaX; vPos.Y = vPos2.Y + vLoc.Y - 2 * iLength + 4 - deltaY; - DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, 0, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); - deltaX += 4; - deltaY -= 2; + + DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, + 0, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); } } } -void ShieldTechnoClass::DrawShieldBarOther(int iLength, Point2D* pLocation, RectangleStruct* pBound) { +void ShieldTechnoClass::DrawShieldBarOther(int iLength, Point2D* pLocation, RectangleStruct* pBound) +{ Point2D vPos = { 0,0 }; Point2D vLoc = *pLocation; int frame, XOffset, YOffset; YOffset = this->Techno->GetTechnoType()->PixelSelectionBracketDelta + this->GetExt()->Shield_BracketDelta; vLoc.Y -= 5; - if (iLength == 8) { + + if (iLength == 8) + { vPos.X = vLoc.X + 11; vPos.Y = vLoc.Y - 25 + YOffset; frame = FileSystem::PIPBRD_SHP->Frames > 2 ? 3 : 1; XOffset = -5; YOffset -= 24; } - else { + else + { vPos.X = vLoc.X + 1; vPos.Y = vLoc.Y - 26 + YOffset; frame = FileSystem::PIPBRD_SHP->Frames > 2 ? 2 : 0; XOffset = -15; YOffset -= 25; } - if (this->Techno->IsSelected) DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPBRD_SHP, frame, &vPos, pBound, BlitterFlags(0xE00), 0, 0, 0, 1000, 0, 0, 0, 0, 0); - int iTotal = int((double)this->HP / this->GetExt()->Shield_Strength * iLength); - if (iTotal < 0) { - iTotal = 0; + if (this->Techno->IsSelected) + { + DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPBRD_SHP, + frame, &vPos, pBound, BlitterFlags(0xE00), 0, 0, 0, 1000, 0, 0, 0, 0, 0); } - if (iTotal > iLength) { + + int iTotal = int(this->GetShieldRatio() * iLength); + int min = this->HP != 0; + + if (iTotal < min) + iTotal = min; + if (iTotal > iLength) iTotal = iLength; - } + frame = this->DrawShieldBar_Pip(); - for (int i = 0; i < iTotal; ++i) { + + for (int i = 0; i < iTotal; ++i) + { vPos.X = vLoc.X + XOffset + 2 * i; vPos.Y = vLoc.Y + YOffset; - DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, frame, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); + + DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, FileSystem::PIPS_SHP, + frame, &vPos, pBound, BlitterFlags(0x600), 0, 0, 0, 1000, 0, 0, 0, 0, 0); } } -int ShieldTechnoClass::DrawShieldBar_Pip() { - auto ShieldPip = RulesExt::Global()->Pips_Shield.Get(); +int ShieldTechnoClass::DrawShieldBar_Pip() +{ + CoordStruct ShieldPip = RulesExt::Global()->Pips_Shield.Get(); + if (this->Techno->WhatAmI() == AbstractType::Building) ShieldPip = RulesExt::Global()->Pips_Shield_Buildings; @@ -428,5 +558,15 @@ int ShieldTechnoClass::DrawShieldBar_Pip() { int ShieldTechnoClass::GetShieldHP() { - return this->HP; + return this->HP; +} + +double ShieldTechnoClass::GetShieldRatio() +{ + return double(this->HP) / double(this->GetExt()->Shield_Strength); +} + +bool ShieldTechnoClass::Available() +{ + return this->Update; } diff --git a/src/Misc/Shield.h b/src/Misc/Shield.h index e350369501..ff559cacf9 100644 --- a/src/Misc/Shield.h +++ b/src/Misc/Shield.h @@ -15,18 +15,23 @@ class ShieldTechnoClass ~ShieldTechnoClass() = default; int ReceiveDamage(args_ReceiveDamage* args); - bool CanBeTargeted(WeaponTypeClass* pWeapon, TechnoClass* pSource); + bool CanBeTargeted(WeaponTypeClass* pWeapon/*, TechnoClass* pSource*/); void AI(); void DrawShieldBar(int iLength, Point2D* pLocation, RectangleStruct* pBound); void InvalidatePointer(void* ptr); - int GetShieldHP(); + int GetShieldHP(); + double GetShieldRatio(); + bool Available(); + + static void SyncShieldToAnother(TechnoClass* pFrom, TechnoClass* pTo); bool Load(PhobosStreamReader& Stm, bool RegisterForChange); bool Save(PhobosStreamWriter& Stm) const; - + private: // static constexpr int ScanInterval = 15; //!< Minimum delay between scans in frames. - struct UninitAnim { + struct UninitAnim + { void operator() (AnimClass* const pAnim) const; }; @@ -42,12 +47,15 @@ class ShieldTechnoClass void WeaponNullifyAnim(); void ResponseAttack(); void TemporalCheck(); + void ConvertCheck(); void DrawShieldBarBuilding(int iLength, Point2D* pLocation, RectangleStruct* pBound); void DrawShieldBarOther(int iLength, Point2D* pLocation, RectangleStruct* pBound); int DrawShieldBar_Pip(); /// Properties /// TechnoClass* Techno; + char TechnoID[0x18]; + bool Update; int HP; TimerStruct Timer_SelfHealing; TimerStruct Timer_Respawn; diff --git a/src/Utilities/GeneralUtils.cpp b/src/Utilities/GeneralUtils.cpp index 2578029ac3..61b6d24fe4 100644 --- a/src/Utilities/GeneralUtils.cpp +++ b/src/Utilities/GeneralUtils.cpp @@ -55,3 +55,8 @@ const int GeneralUtils::GetRangedRandomOrSingleValue(Point2D range) return range.X >= range.Y ? range.X : ScenarioClass::Instance->Random.RandomRanged(range.X, range.Y); } + +const double GeneralUtils::GetWarheadVersusArmor(WarheadTypeClass* pWH, int ArmorType) +{ + return double(MapClass::GetTotalDamage(100, pWH, ArmorType, 0)) / 100.0; +} diff --git a/src/Utilities/GeneralUtils.h b/src/Utilities/GeneralUtils.h index a4ab941747..726187e673 100644 --- a/src/Utilities/GeneralUtils.h +++ b/src/Utilities/GeneralUtils.h @@ -23,4 +23,5 @@ class GeneralUtils static const wchar_t* LoadStringUnlessMissing(char* key, const wchar_t* defaultValue); static std::vector AdjacentCellsInRange(unsigned int range); static const int GetRangedRandomOrSingleValue(Point2D range); + static const double GetWarheadVersusArmor(WarheadTypeClass* pWH, int ArmorType); }; \ No newline at end of file