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
+
+
+*`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;
+}