diff --git a/CREDITS.md b/CREDITS.md index 5a508b272e..71798d2069 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -396,6 +396,7 @@ This page lists all the individual contributions to the project by their author. - Fast access structure - Iron Curtain/Custom Tint Support for SHP Turreted Vehicles - Reactivate unused trigger events 2, 53, and 54 + - Weapons now support `AttackFriendlies` and `AttackCursorOnFriendlies` - **NetsuNegi**: - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 058b236e44..f60bbe8f12 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2575,6 +2575,17 @@ In `rulesmd.ini`: AreaFire.Target=base ; AreaFire Target Enumeration (base|self|random) ``` +### Can attack allies + +- Weapons now support `AttackFriendlies` and `AttackCursorOnFriendlies`, They override the firer's `AttackFriendlies` and `AttackCursorOnFriendlies`. + +In `rulesmd.ini`: +```ini +[SOMEWEAPON] ; WeaponType +AttackFriendlies= ; boolean +AttackCursorOnFriendlies= ; boolean +``` + ### Burst delay customizations - `Burst.Delays` allows specifying weapon-specific burst shot delays. Takes precedence over the old `BurstDelayX` logic available on VehicleTypes, functions with Infantry & BuildingType weapons (AircraftTypes are not supported due to their weapon firing system being completely different) and allows every shot of `Burst` to have a separate delay instead of only first four shots. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index ce89f81c75..318c7929eb 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -457,6 +457,7 @@ New: - Fast access structure (by FlyStar) - Toggle off laser trail and shake effects (by Ollerus) - [Dehardcode the `ZAdjust` of warhead anim](Fixed-or-Improved-Logics.md#dehardcode-the-zadjust-of-warhead-anim) (by TaranDahl) +- Weapons now support `AttackFriendlies` and `AttackCursorOnFriendlies` (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/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index d495a14c98..5d22ece0ce 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1552,3 +1552,72 @@ DEFINE_HOOK(0x6F9398, TechnoClass_SelectAutoTarget_Scan_FallingDown, 0x9) } #pragma endregion + +#pragma region AutoTargetExtension + +DEFINE_JUMP(LJMP, 0x700387, 0x7003BD) + +DEFINE_HOOK(0x700358, TechnoClass_MouseOverObject_AttackFriendlies, 0x6) +{ + enum { CanAttack = 0x700381, Continue = 0x700385 }; + + GET(TechnoClass*, pThis, ESI); + GET(WeaponTypeClass*, pWeapon, EBP); + GET_STACK(const bool, IvanBomb, STACK_OFFSET(0x1C, -0xC)); + + const auto pType = pThis->GetTechnoType(); + const auto pWeaponTypeExt = WeaponTypeExt::ExtMap.Find(pWeapon); + + if (pWeaponTypeExt->AttackFriendlies.Get(pType->AttackFriendlies) + || (pWeaponTypeExt->AttackCursorOnFriendlies.Get(pType->AttackCursorOnFriendlies) && !IvanBomb)) + { + return CanAttack; + } + + return Continue; +} + +DEFINE_HOOK_AGAIN(0x6F9CE9, TechnoClass_CheckAutoTarget_AttackFriendlies, 0xA) // TechnoClass::SelectAutoTarget +DEFINE_HOOK_AGAIN(0x6F9BAE, TechnoClass_CheckAutoTarget_AttackFriendlies, 0xA) +DEFINE_HOOK_AGAIN(0x6F9204, TechnoClass_CheckAutoTarget_AttackFriendlies, 0xA) +DEFINE_HOOK_AGAIN(0x6F8BBC, TechnoClass_CheckAutoTarget_AttackFriendlies, 0xA) // TechnoClass::TryAutoTargetObject +DEFINE_HOOK(0x6F8A92, TechnoClass_CheckAutoTarget_AttackFriendlies, 0xA) +{ + GET(TechnoClass*, pThis, ESI); + + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + R->CL(pThis->Veterancy.IsElite() ? pTypeExt->AttackFriendlies.Y : pTypeExt->AttackFriendlies.X); + return R->Origin() + 0x10; +} + +namespace CanAutoTargetTemp +{ + WeaponTypeExt::ExtData* WeaponExt; +} + +DEFINE_HOOK(0x6F7E30, TechnoClass_CanAutoTarget_SetContent, 0x6) +{ + GET(WeaponTypeClass*, pWeapon, EBP); + + CanAutoTargetTemp::WeaponExt = WeaponTypeExt::ExtMap.TryFind(pWeapon); + + return 0; +} + +DEFINE_HOOK(0x6F7EF4, TechnoClass_CanAutoTarget_AttackFriendlies, 0xA) +{ + enum { SkipGameCode = 0x6F7F04 }; + + GET(TechnoClass*, pThis, EDI); + + bool attackFriendlies = pThis->GetTechnoType()->AttackFriendlies; + + if (const auto pWeaponExt = CanAutoTargetTemp::WeaponExt) + attackFriendlies = pWeaponExt->AttackFriendlies.Get(attackFriendlies); + + R->CL(attackFriendlies); + return SkipGameCode; +} + +#pragma endregion diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index cf59f3baac..a45c910041 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -373,37 +374,60 @@ void TechnoTypeExt::ExtData::UpdateAdditionalAttributes() const auto pThis = this->OwnerObject(); int count = 2; + const bool attackFriendlies = pThis->AttackFriendlies; + this->AttackFriendlies = { attackFriendlies ,attackFriendlies }; + if (this->MultiWeapon && (!pThis->IsGattling && (!pThis->HasMultipleTurrets() || !pThis->Gunner))) { count = pThis->WeaponCount; } - for (int index = 0; index < count; index++) + auto WeaponCheck = [&](WeaponTypeClass* const pWeapon, const bool isElite) { - const auto pWeapon = pThis->GetWeapon(index)->WeaponType; - auto pEliteWeapon = pThis->GetEliteWeapon(index)->WeaponType; + if (!pWeapon) + return; - if (!pEliteWeapon) - pEliteWeapon = pWeapon; + if (isElite) + { + if (pWeapon->Projectile) + this->ThreatTypes.Y |= pWeapon->AllowedThreats(); - if (pWeapon) + this->CombatDamages.Y += (pWeapon->Damage + pWeapon->AmbientDamage); + eliteNum++; + + if (!this->AttackFriendlies.Y + && WeaponTypeExt::ExtMap.Find(pWeapon)->AttackFriendlies.Get(false)) + { + this->AttackFriendlies.Y = true; + } + } + else { if (pWeapon->Projectile) this->ThreatTypes.X |= pWeapon->AllowedThreats(); this->CombatDamages.X += (pWeapon->Damage + pWeapon->AmbientDamage); num++; + + if (!this->AttackFriendlies.X + && WeaponTypeExt::ExtMap.Find(pWeapon)->AttackFriendlies.Get(false)) + { + this->AttackFriendlies.X = true; + } } + }; - if (pEliteWeapon) - { - if (pEliteWeapon->Projectile) - this->ThreatTypes.Y |= pEliteWeapon->AllowedThreats(); + for (int index = 0; index < count; index++) + { + const auto pWeapon = pThis->GetWeapon(index)->WeaponType; + auto pEliteWeapon = pThis->GetEliteWeapon(index)->WeaponType; - this->CombatDamages.Y += (pEliteWeapon->Damage + pEliteWeapon->AmbientDamage); - eliteNum++; - } + if (!pEliteWeapon) + pEliteWeapon = pWeapon; + + WeaponCheck(pWeapon, false); + WeaponCheck(pEliteWeapon, true); } if (num > 0) @@ -1658,6 +1682,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->InfantryAutoDeploy) .Process(this->TurretResponse) + + .Process(this->AttackFriendlies) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index efe25a53e1..c0434219ed 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -434,6 +434,8 @@ class TechnoTypeExt Nullable TurretResponse; + Vector2D AttackFriendlies; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -818,6 +820,8 @@ class TechnoTypeExt , InfantryAutoDeploy {} , TurretResponse {} + + , AttackFriendlies { false,false } { } virtual ~ExtData() = default; diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 7f2037c577..150b540944 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -153,6 +153,8 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DelayedFire_OnlyOnInitialBurst.Read(exINI, pSection, "DelayedFire.OnlyOnInitialBurst"); this->DelayedFire_AnimOffset.Read(exINI, pSection, "DelayedFire.AnimOffset"); this->DelayedFire_AnimOnTurret.Read(exINI, pSection, "DelayedFire.AnimOnTurret"); + this->AttackFriendlies.Read(exINI, pSection, "AttackFriendlies"); + this->AttackCursorOnFriendlies.Read(exINI, pSection, "AttackCursorOnFriendlies"); // handle SkipWeaponPicking if (this->CanTarget != AffectedTarget::All || this->CanTargetHouses != AffectedHouse::All @@ -237,6 +239,8 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->DelayedFire_OnlyOnInitialBurst) .Process(this->DelayedFire_AnimOffset) .Process(this->DelayedFire_AnimOnTurret) + .Process(this->AttackFriendlies) + .Process(this->AttackCursorOnFriendlies) ; }; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 03d6fe37e0..cc537295e3 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -91,6 +91,8 @@ class WeaponTypeExt Valueable DelayedFire_OnlyOnInitialBurst; Nullable DelayedFire_AnimOffset; Valueable DelayedFire_AnimOnTurret; + Nullable AttackFriendlies; + Nullable AttackCursorOnFriendlies; bool SkipWeaponPicking; @@ -164,6 +166,8 @@ class WeaponTypeExt , DelayedFire_OnlyOnInitialBurst { false } , DelayedFire_AnimOffset {} , DelayedFire_AnimOnTurret { true } + , AttackFriendlies {} + , AttackCursorOnFriendlies {} { } int GetBurstDelay(int burstIndex) const;