diff --git a/CREDITS.md b/CREDITS.md index 7610533f1c..04b6ddb79b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -355,6 +355,7 @@ This page lists all the individual contributions to the project by their author. - `Edit/Clear Hate-Value` Trigger Action - `Set Force Enemy` Trigger Action - Fix the issue where computer players did not search for new enemies after defeating them or forming alliances with them + - Use WeaponX freely - **NetsuNegi**: - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 19f9790f18..1c4ad64643 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -24,6 +24,8 @@ + + diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index e3585e2f21..f986623898 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1739,6 +1739,22 @@ WarpInWeapon.UseDistanceAsDamage=false ; boolean WarpOutWeapon= ; WeaponType ``` +### Multi Weapon + +![Multi Weapon](_static/images/multiweapon.gif) +*`NoAmmoWeapon=2` can be used normally after `MultiWeapon=yes` in a private mod by @Stormsulfur* + +- You are free to decide whether to use Weapon x or not, instead of passively using Primary/secondary. + - TechnoType reads `WeaponX` as their weapon when `MultiWeapon=yes`, be careful not to forget `WeaponCount`. + - `MultiWeapon.IsSecondary` can only be used for infantry and is responsible for determining which weapons should use `SecondaryFire` in the `Sequence`. + +In `rulesmd.ini`: +```ini +[SOMETECHNO] ; TechnoType +MultiWeapon= ; boolean +MultiWeapon.IsSecondary= ; list of integer +``` + ## Terrain ### Destroy animation & sound diff --git a/docs/Whats-New.md b/docs/Whats-New.md index ed4a9aa7b9..7f429c4179 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -372,6 +372,7 @@ New: - [Amphibious access vehicle](New-or-Enhanced-Logics.md#amphibious-access-vehicle) (by CrimRecya) - [Allow miners do area guard](Fixed-or-Improved-Logics.md#allow-miners-do-area-guard) (by TaranDahl) - [Make harvesters do addtional scan after unload](Fixed-or-Improved-Logics.md#make-harvesters-do-addtional-scan-after-unload) (by TaranDahl) +- Use WeaponX freely (by FlyStar) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/docs/_static/images/multiweapon.gif b/docs/_static/images/multiweapon.gif new file mode 100644 index 0000000000..3fb1aecd3d Binary files /dev/null and b/docs/_static/images/multiweapon.gif differ diff --git a/src/Ext/Techno/Body.MultiWeapon.cpp b/src/Ext/Techno/Body.MultiWeapon.cpp new file mode 100644 index 0000000000..be1e889214 --- /dev/null +++ b/src/Ext/Techno/Body.MultiWeapon.cpp @@ -0,0 +1,151 @@ +#include "Body.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +bool TechnoExt::IsSecondary(TechnoClass* const pThis, const int& nWeaponIndex) +{ + if (!pThis) + return false; + + if (pThis->GetTechnoType()->IsGattling) + return nWeaponIndex != 0 && nWeaponIndex % 2 != 0; + + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + if (pTypeExt && pTypeExt->MultiWeapon.Get() && + !pTypeExt->MultiWeapon_IsSecondary.empty()) + { + int index = nWeaponIndex + 1; + return pTypeExt->MultiWeapon_IsSecondary.Contains(index); + } + + return nWeaponIndex != 0; +} + +// maybe it could be used as a new SelectWeapon, but not for now. +bool TechnoExt::CheckMultiWeapon(TechnoClass* const pThis, AbstractClass* const pTarget, WeaponTypeClass* const pWeaponType) +{ + if (!pThis || !pWeaponType || pWeaponType->NeverUse || + !pWeaponType->Projectile || !pWeaponType->Warhead || + (pThis->InOpenToppedTransport && !pWeaponType->FireInTransport)) + return false; + + const auto pWH = pWeaponType->Warhead; + + if (TechnoClass* pTargetTechno = abstract_cast(pTarget)) + { + if (pTargetTechno->Health <= 0 || !pTargetTechno->IsAlive) + return false; + + if (const auto pTargetExt = TechnoExt::ExtMap.Find(pTargetTechno)) + { + const auto pShield = pTargetExt->Shield.get(); + + if (pShield && pShield->IsActive() && + !pShield->CanBeTargeted(pWeaponType)) + return false; + } + + if (GeneralUtils::GetWarheadVersusArmor(pWH, pTargetTechno->GetTechnoType()->Armor) == 0.0) + return false; + + if (pTargetTechno->IsInAir()) + { + if (!pWeaponType->Projectile->AA) + return false; + } + else + { + const auto pBulletTypeExt = BulletTypeExt::ExtMap.Find(pWeaponType->Projectile); + + if (!pBulletTypeExt->AAOnly.Get()) + return false; + } + + if (pWH->IsLocomotor && pTarget->WhatAmI() == AbstractType::Building) + return false; + + if (pWH->ElectricAssault) + { + if (!pThis->Owner->IsAlliedWith(pTargetTechno->Owner)) + return false; + + if (pTargetTechno->WhatAmI() != AbstractType::Building) + return false; + + const auto pBld = abstract_cast(pTargetTechno); + + if (!pBld || !pBld->Type || !pBld->Type->Overpowerable) + return false; + } + + if (pWeaponType->DrainWeapon && (pTargetTechno->DrainingMe || + !pTargetTechno->GetTechnoType()->Drainable)) + return false; + + if (pTargetTechno->AttachedBomb) + { + if (pWH->IvanBomb) + return false; + + if (pWH->BombDisarm && + !pThis->Owner->IsControlledByHuman() && + !pThis->Owner->IsAlliedWith(pTargetTechno->Owner)) + return false; + } + else + { + if (pWH->BombDisarm) + return false; + } + + if (pWH->Airstrike && pTargetTechno->IsInAir()) + return false; + + if (pWH->MindControl && (pTargetTechno->Owner == pThis->Owner || + pTargetTechno->IsMindControlled() || pTargetTechno->GetTechnoType()->ImmuneToPsionics)) + return false; + + if (pWH->Parasite && (pTargetTechno->WhatAmI() == AbstractType::Building || + abstract_cast(pTargetTechno)->ParasiteEatingMe)) + return false; + + const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeaponType); + + if (pWeaponExt && !pWeaponExt->HasRequiredAttachedEffects(pTargetTechno, pThis)) + return false; + } + else + { + if (pWH->ElectricAssault || pWH->BombDisarm || pWH->Airstrike || pWeaponType->DrainWeapon) + return false; + + if (pTarget->WhatAmI() == AbstractType::Terrain) + { + if (!pWH->Wood) + return false; + } + else if (pTarget->WhatAmI() == AbstractType::Cell) + { + const auto pCell = abstract_cast(pTarget); + + if (pCell && pCell->OverlayTypeIndex >= 0) + { + auto overlayType = OverlayTypeClass::Array.GetItem(pCell->OverlayTypeIndex); + + if (overlayType->Wall && !pWH->Wall) + return false; + } + } + } + + return true; +} diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index c8ad3f5c4c..ff78b79ac2 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -222,4 +222,7 @@ class TechnoExt static int GetWeaponIndexAgainstWall(TechnoClass* pThis, OverlayTypeClass* pWallOverlayType); static void ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static void ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); + + static bool CheckMultiWeapon(TechnoClass* const pThis, AbstractClass* const pTarget, WeaponTypeClass* const pWeaponType); + static bool IsSecondary(TechnoClass* const pThis, const int& nWeaponIndex); }; diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index af7b5dce16..5a98283883 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -41,6 +41,20 @@ DEFINE_HOOK(0x6F3339, TechnoClass_WhatWeaponShouldIUse_Interceptor, 0x8) return SkipGameCode; } +DEFINE_HOOK(0x6F3360, TechnoClass_WhatWeaponShouldIUse_MultiWeapon, 0x6) +{ + GET(TechnoClass*, pThis, ESI); + enum { SkipGameCode = 0x6F3379 }; + + auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + if (pTypeExt->MultiWeapon.Get() && + (pThis->WhatAmI() != AbstractType::Unit || !pThis->GetTechnoType()->Gunner)) + return SkipGameCode; + + return 0; +} + DEFINE_HOOK(0x6F33CD, TechnoClass_WhatWeaponShouldIUse_ForceFire, 0x6) { enum { ReturnWeaponIndex = 0x6F37AF }; diff --git a/src/Ext/Techno/Hooks.MultiWeapon.cpp b/src/Ext/Techno/Hooks.MultiWeapon.cpp new file mode 100644 index 0000000000..0177158d1b --- /dev/null +++ b/src/Ext/Techno/Hooks.MultiWeapon.cpp @@ -0,0 +1,71 @@ +#include "Body.h" + +// There was no way to do it, so I decided to leave it at that. +/* +bool _fastcall CanElectricAssault(FootClass* pThis) +{ + const auto pType = pThis->GetTechnoType(); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + bool allowAssault = !pThis->GetTechnoType()->IsGattling && + (pThis->WhatAmI() != AbstractType::Unit || !pThis->GetTechnoType()->Gunner); + + if (pTypeExt->MultiWeapon.Get() && allowAssault) + { + for (int index = 0; index < pThis->GetTechnoType()->WeaponCount; index++) + { + const auto pWeaponType = pThis->Veterancy.IsElite() ? + pType->GetEliteWeapon(index)->WeaponType : pType->GetWeapon(index)->WeaponType; + + if (!pWeaponType || !pWeaponType->Warhead || + !pWeaponType->Warhead->ElectricAssault) + continue; + + return true; + } + } + else if (allowAssault) + { + const auto secondary = pThis->GetWeapon(1)->WeaponType; + + if (secondary && secondary->Warhead && + secondary->Warhead->ElectricAssault) + return true; + } + + return false; +} + +DEFINE_HOOK(0x4D50E1, FootClass_Mission_Guard_ElectricAssult, 0xA) +{ + GET(FootClass*, pThis, ESI); + enum { ElectricAssult = 0x4D5116, Skip = 0x4D51D4, SkipAll = 0x4D52F5 }; + + if (!pThis) + return SkipAll; + + return CanElectricAssault(pThis) ? ElectricAssult : Skip; +} + +DEFINE_HOOK(0x4D6F44, FootClass_Mission_AreaGuard_ElectricAssult, 0x6) +{ + GET(FootClass*, pThis, ESI); + enum { ElectricAssult = 0x4D6F78, Skip = 0x4D7025, SkipAll = 0x4D715A }; + + if (!pThis) + return SkipAll; + + return CanElectricAssault(pThis) ? ElectricAssult : Skip; +} +*/ + +// Do you think the infantry's way of determining that weapons are secondary is stupid ? +// I think it's kind of stupid. +DEFINE_HOOK(0x520888, InfantryClass_UpdateFiring_IsSecondary, 0x8) +{ + GET(InfantryClass*, pThis, EBP); + GET(int, weaponIdx, EDI); + enum { Primary = 0x5208D6, Secondary = 0x520890 }; + + R->AL(pThis->Crawling); + return TechnoExt::IsSecondary(pThis, weaponIdx) ? Secondary : Primary; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index bd96fbba53..a74dfa0734 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -59,7 +59,7 @@ void TechnoTypeExt::ExtData::ParseBurstFLHs(INI_EX& exArtINI, const char* pArtSe char tempBuffer[32]; char tempBufferFLH[48]; auto pThis = this->OwnerObject(); - bool parseMultiWeapons = pThis->TurretCount > 0 && pThis->WeaponCount > 0; + bool parseMultiWeapons = this->MultiWeapon.Get() || (pThis->TurretCount > 0 && pThis->WeaponCount > 0); auto weaponCount = parseMultiWeapons ? pThis->WeaponCount : 2; nFLH.resize(weaponCount); nEFlh.resize(weaponCount); @@ -1019,9 +1019,14 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Overload_DeathSound) .Process(this->Overload_ParticleSys) .Process(this->Overload_ParticleSysCount) - + .Process(this->Harvester_CanGuardArea) .Process(this->HarvesterScanAfterUnload) + + .Process(this->MultiWeapon) + .Process(this->MultiWeapon_IsSecondary) + //.Process(this->MultiWeapon_SelectWeapon) + .Process(this->LastMultiWeapon) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 930925d395..e7ae477b4b 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -343,9 +343,14 @@ class TechnoTypeExt NullableIdx Overload_DeathSound; Nullable Overload_ParticleSys; Valueable Overload_ParticleSysCount; - + Valueable Harvester_CanGuardArea; Nullable HarvesterScanAfterUnload; + + Valueable MultiWeapon; + ValueableVector MultiWeapon_IsSecondary; + //Valueable MultiWeapon_SelectWeapon; + bool LastMultiWeapon; ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } @@ -644,9 +649,14 @@ class TechnoTypeExt , Overload_DeathSound {} , Overload_ParticleSys {} , Overload_ParticleSysCount { 5 } - + , Harvester_CanGuardArea { false } , HarvesterScanAfterUnload {} + + , MultiWeapon { false } + , MultiWeapon_IsSecondary {} + //, MultiWeapon_SelectWeapon { false } + , LastMultiWeapon { false } { } virtual ~ExtData() = default; diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index bfaa7dcc6c..412a84a653 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -132,3 +132,61 @@ DEFINE_HOOK(0x71464A, TechnoTypeClass_ReadINI_Speed, 0x7) return SkipGameCode; } + +DEFINE_HOOK(0x7128B2, TechnoTypeClass_ReadINI_MultiWeapon, 0x6) +{ + GET(TechnoTypeClass*, pThis, EBP); + GET(CCINIClass*, pINI, ESI); + enum { ReadWeaponX = 0x7128C0 }; + + INI_EX exINI(pINI); + const char* pSection = pThis->ID; + + if (const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis)) + { + pTypeExt->MultiWeapon.Read(exINI, pSection, "MultiWeapon"); + + if (pTypeExt->MultiWeapon.Get()) + { + pTypeExt->MultiWeapon_IsSecondary.Read(exINI, pSection, "MultiWeapon.IsSecondary"); + //pTypeExt->MultiWeapon_SelectWeapon.Read(exINI, pSection, "MultiWeapon.SelectWeapon"); + + if (!pThis->IsGattling && + pTypeExt->MultiWeapon.Get() != pTypeExt->LastMultiWeapon) + { + auto clearWeapon = [pThis](int index) + { + auto& pWeapon = pThis->GetWeapon(index, false); + auto& pEliteWeapon = pThis->GetWeapon(index, true); + + pWeapon = WeaponStruct(); + pEliteWeapon = WeaponStruct(); + }; + + clearWeapon(0); + clearWeapon(1); + } + + return ReadWeaponX; + } + } + + return 0; +} + +DEFINE_HOOK(0x715B10, TechnoTypeClass_ReadINI_MultiWeapon2, 0x7) +{ + GET(TechnoTypeClass*, pThis, EBP); + enum { ReadWeaponX = 0x715B1F, Continue = 0x715B17 }; + + if (const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis)) + { + pTypeExt->LastMultiWeapon = pTypeExt->MultiWeapon.Get(); + + if (pTypeExt->MultiWeapon.Get()) + return ReadWeaponX; + } + + R->AL(pThis->HasMultipleTurrets()); + return Continue; +}