diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2d314b67d1..52818ffeaa 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2618,6 +2618,7 @@ OmniFire.TurnToTarget=no ; boolean - `Strafing.Shots` controls the number of times the weapon is fired during a single strafe run, defaults to 5 if not set. `Ammo` is only deducted at the end of the strafe run, regardless of the number of shots fired. - `Strafing.SimulateBurst` controls whether or not the shots fired during strafing simulate behavior of `Burst`, allowing for alternating firing offset. Only takes effect if weapon has `Burst` set to 1 or undefined. - `Strafing.UseAmmoPerShot`, if set to `true` overrides the usual behaviour of only deducting ammo after a strafing run and instead doing it after each individual shot. + - `Strafing.TargetCell` controls whether the aircraft will change the target of this round to the ground after firing the first shot, to ensure that all `Strafing.Shots` can be dropped. That is, the `Strafing` will not be interrupted by the premature death of the target. - `Strafing.EndDelay` can be used to override the delay after firing last shot in strafing run before aircraft resumes another strafing run or returns to base. Defaults to (Weapon `Range` * 256 + 1024) / Aircraft `Speed`. Note that using a short delay with aircraft that can do multiple strafing runs with their ammo can cause undesired behaviour like dancing around or facing weird way depending on other factors like ROF and/or movement speed. - There is a special case for aircraft spawned by `Type=SpyPlane` superweapons on `SpyPlane Approach` or `SpyPlane Overfly` mission where `Strafing.Shots` only if explicitly set on its primary weapon, determines the maximum number of times the map revealing effect can activate irregardless of other factors. @@ -2628,6 +2629,7 @@ Strafing= ; boolean Strafing.Shots= ; integer Strafing.SimulateBurst=false ; boolean Strafing.UseAmmoPerShot=false ; boolean +Strafing.TargetCell=false ; boolean Strafing.EndDelay= ; integer, game frames ``` diff --git a/src/Ext/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp index 397bc46f54..413ebde55e 100644 --- a/src/Ext/Aircraft/Hooks.cpp +++ b/src/Ext/Aircraft/Hooks.cpp @@ -194,6 +194,19 @@ DEFINE_FUNCTION_JUMP(CALL6, 0x4188D3, AircraftClass_SelectWeapon_Wrapper); DEFINE_FUNCTION_JUMP(CALL6, 0x4189E2, AircraftClass_SelectWeapon_Wrapper); DEFINE_FUNCTION_JUMP(CALL6, 0x418AF1, AircraftClass_SelectWeapon_Wrapper); +DEFINE_HOOK_AGAIN(0x41874E, AircraftClass_Mission_Attack_StrafingDestinationFix, 0x6) +DEFINE_HOOK(0x418544, AircraftClass_Mission_Attack_StrafingDestinationFix, 0x6) +{ + GET(FireError, fireError, EAX); + GET(AircraftClass*, pThis, ESI); + + // The aircraft managed by the spawn manager will not update destination after changing target + if (fireError == FireError::RANGE && pThis->Is_Strafe()) + pThis->SetDestination(pThis->Target, true); + + return 0; +} + #pragma region After_Shot_Delays static int GetDelay(AircraftClass* pThis, bool isLastShot) @@ -205,6 +218,7 @@ static int GetDelay(AircraftClass* pThis, bool isLastShot) if (isLastShot || pExt->Strafe_BombsDroppedThisRound == pWeaponExt->Strafing_Shots.Get(5) || (pWeaponExt->Strafing_UseAmmoPerShot && !pThis->Ammo)) { + pExt->Strafe_TargetCell = nullptr; pThis->MissionStatus = (int)AirAttackStatus::FlyToPosition; delay = pWeaponExt->Strafing_EndDelay.Get((pWeapon->Range + (Unsorted::LeptonsPerCell * 4)) / pThis->Type->Speed); } @@ -216,6 +230,11 @@ DEFINE_HOOK(0x4184CC, AircraftClass_Mission_Attack_Delay1A, 0x6) { GET(AircraftClass*, pThis, ESI); + auto const pExt = TechnoExt::ExtMap.Find(pThis); + + if (WeaponTypeExt::ExtMap.Find(pThis->GetWeapon(pExt->CurrentAircraftWeaponIndex)->WeaponType)->Strafing_TargetCell) + pExt->Strafe_TargetCell = MapClass::Instance.GetCellAt(pThis->Target->GetCoords()); + pThis->IsLocked = true; pThis->MissionStatus = (int)AirAttackStatus::FireAtTarget2_Strafe; R->EAX(GetDelay(pThis, false)); @@ -275,10 +294,55 @@ DEFINE_HOOK(0x418B8A, AircraftClass_Mission_Attack_Delay5, 0x6) #pragma endregion -DEFINE_HOOK_AGAIN(0x41882C, AircraftClass_MissionAttack_ScatterCell1, 0x6); -DEFINE_HOOK_AGAIN(0x41893B, AircraftClass_MissionAttack_ScatterCell1, 0x6); -DEFINE_HOOK_AGAIN(0x418A4A, AircraftClass_MissionAttack_ScatterCell1, 0x6); -DEFINE_HOOK_AGAIN(0x418B46, AircraftClass_MissionAttack_ScatterCell1, 0x6); +#pragma region StrafeCell + +DEFINE_HOOK_AGAIN(0x4188AC, AircraftClass_Mission_Attack_StrafeCell, 0x6) +DEFINE_HOOK_AGAIN(0x4189BB, AircraftClass_Mission_Attack_StrafeCell, 0x6) +DEFINE_HOOK_AGAIN(0x418ACA, AircraftClass_Mission_Attack_StrafeCell, 0x6) +DEFINE_HOOK(0x41879D, AircraftClass_Mission_Attack_StrafeCell, 0x6) +{ + enum { CannotFireNow = 0x418BC5, SkipGameCode = 0x418BBA }; + + GET(AircraftClass*, pThis, ESI); + + const auto pExt = TechnoExt::ExtMap.Find(pThis); + + if (const auto pTargetCell = pExt->Strafe_TargetCell) + { + switch (pThis->GetFireError(pTargetCell, pExt->CurrentAircraftWeaponIndex, true)) + { + case FireError::OK: + case FireError::FACING: + case FireError::CLOAKED: + case FireError::RANGE: + break; + default: + return CannotFireNow; + } + + AircraftExt::FireWeapon(pThis, pTargetCell); + + if (pExt->TypeExtData->FiringForceScatter) + pTargetCell->ScatterContent(pThis->Location, true, false, false); + + pThis->SetDestination(pTargetCell, true); + pThis->MissionStatus++; + + R->EAX(GetDelay(pThis, pThis->MissionStatus > static_cast(AirAttackStatus::FireAtTarget5_Strafe))); + return SkipGameCode; + } + + return 0; +} + +#pragma endregion + +#pragma region ScatterCell + +DEFINE_HOOK_AGAIN(0x41882C, AircraftClass_MissionAttack_ScatterCell1, 0x6) +DEFINE_HOOK_AGAIN(0x41893B, AircraftClass_MissionAttack_ScatterCell1, 0x6) +DEFINE_HOOK_AGAIN(0x418A4A, AircraftClass_MissionAttack_ScatterCell1, 0x6) +DEFINE_HOOK_AGAIN(0x418B46, AircraftClass_MissionAttack_ScatterCell1, 0x6) DEFINE_HOOK(0x41847E, AircraftClass_MissionAttack_ScatterCell1, 0x6) { GET(AircraftClass*, pThis, ESI); @@ -315,7 +379,6 @@ DEFINE_HOOK(0x414C0B, AircraftClass_ChronoSparkleDelay, 0x5) return 0x414C10; } - #pragma region LandingDir DEFINE_HOOK(0x4CF31C, FlyLocomotionClass_FlightUpdate_LandingDir, 0x9) @@ -545,7 +608,7 @@ DEFINE_HOOK(0x4CF190, FlyLocomotionClass_FlightUpdate_SetPrimaryFacing, 0x6) // destination.Y += cellOffset.Y; } - if (footCoords.Y != destination.Y && footCoords.X != destination.X) + if (footCoords.Y != destination.Y || footCoords.X != destination.X) pAircraft->PrimaryFacing.SetDesired(DirStruct(Math::atan2(footCoords.Y - destination.Y, destination.X - footCoords.X))); else pAircraft->PrimaryFacing.SetDesired(landingDir); diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 71c9adde72..1130f2c27d 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -784,6 +784,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->MindControlRingAnimType) .Process(this->DamageNumberOffset) .Process(this->Strafe_BombsDroppedThisRound) + .Process(this->Strafe_TargetCell) .Process(this->CurrentAircraftWeaponIndex) .Process(this->IsInTunnel) .Process(this->IsBurrowed) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 9e02a64317..46c9d19957 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -42,6 +42,7 @@ class TechnoExt AnimTypeClass* MindControlRingAnimType; int DamageNumberOffset; int Strafe_BombsDroppedThisRound; + CellClass* Strafe_TargetCell; int CurrentAircraftWeaponIndex; bool IsInTunnel; bool IsBurrowed; @@ -108,6 +109,7 @@ class TechnoExt , MindControlRingAnimType { nullptr } , DamageNumberOffset { INT32_MIN } , Strafe_BombsDroppedThisRound { 0 } + , Strafe_TargetCell { nullptr } , CurrentAircraftWeaponIndex {} , IsInTunnel { false } , IsBurrowed { false } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index b880bab0a9..be24ebc24f 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -98,6 +98,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Strafing_Shots.Read(exINI, pSection, "Strafing.Shots"); this->Strafing_SimulateBurst.Read(exINI, pSection, "Strafing.SimulateBurst"); this->Strafing_UseAmmoPerShot.Read(exINI, pSection, "Strafing.UseAmmoPerShot"); + this->Strafing_TargetCell.Read(exINI, pSection, "Strafing.TargetCell"); this->Strafing_EndDelay.Read(exINI, pSection, "Strafing.EndDelay"); this->CanTarget.Read(exINI, pSection, "CanTarget"); this->CanTargetHouses.Read(exINI, pSection, "CanTargetHouses"); @@ -179,6 +180,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->Strafing_Shots) .Process(this->Strafing_SimulateBurst) .Process(this->Strafing_UseAmmoPerShot) + .Process(this->Strafing_TargetCell) .Process(this->Strafing_EndDelay) .Process(this->CanTarget) .Process(this->CanTargetHouses) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 33f99a4567..03d6fe37e0 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -36,6 +36,7 @@ class WeaponTypeExt Nullable Strafing_Shots; Valueable Strafing_SimulateBurst; Valueable Strafing_UseAmmoPerShot; + Valueable Strafing_TargetCell; Nullable Strafing_EndDelay; Valueable CanTarget; Valueable CanTargetHouses; @@ -107,6 +108,7 @@ class WeaponTypeExt , Strafing_Shots {} , Strafing_SimulateBurst { false } , Strafing_UseAmmoPerShot { false } + , Strafing_TargetCell { false } , Strafing_EndDelay {} , CanTarget { AffectedTarget::All } , CanTargetHouses { AffectedHouse::All }