diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 7ad4e6071e..819c7037b0 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -14,6 +14,8 @@ + + @@ -78,6 +80,7 @@ + diff --git a/README.md b/README.md index 7959e0bbb8..327226c265 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Credits - AutoGavy - interceptor logic, warhead critical damage system - Xkein - general assistance, YRpp edits - thomassneddon - general assistance -- Starkku - Warhead shield penetration & breaking +- Starkku - Warhead shield penetration & breaking, strafing aircraft weapon customization Thanks to everyone who uses Phobos, tests changes and reports bugs! You can show your appreciation and help project by displaying the logo (monochrome version can be found [here](https://github.com/Phobos-developers/Phobos/logo-mono.png)) in your client/launcher, contributing or donating to us via links on the right and the `Sponsor` button on top of the repo. diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index cc19930e1f..415ff0cc4a 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -9,6 +9,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Allowed usage of TileSet of 255 and above without making NE-SW broken bridges unrepairable. - `TurretOffset` tag for voxel turreted technos now accepts FLH (forward, lateral, height) values like `TurretOffset=F,L` or `TurretOffset=F,L,H`, which means turret location can be adjusted in all three axes. - `InfiniteMindControl` with `Damage=1` can now control more than 1 unit. +- Aircraft with `Fighter` set to false or those using strafing pattern (weapon projectile `ROT` is below 2) now take weapon's `Burst` into accord for all shots instead of just the first one. ![image](_static/images/remember-target-after-deploying-01.gif) *Nod arty keeping target on attack order in [C&C: Reloaded](https://www.moddb.com/mods/cncreloaded/)* diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 6290703fd0..721364d064 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -117,6 +117,22 @@ BreaksShield=false ; boolean ## Weapons +### Strafing aircraft weapon customization + +![image](_static/images/strafing-01.gif) +*Strafing aircraft weapon customization in [Project Phantom](https://www.moddb.com/mods/project-phantom)* + +- Some of the behaviour of strafing aircraft weapons (weapon projectile has `ROT` below 2) can now be customized. + - `Strafing.Shots` controls the number of times the weapon is fired during a single strafe run. `Ammo` is only deducted at the end of the strafe run, regardless of the number of shots fired. Valid values range from 1 to 5, any values smaller or larger are effectively treated same as either 1 or 5, respectively. Defaults to 5. + - `Strafing.SimulateBurst` controls whether or not the shots fired during strafing simulate behaviour of `Burst`, allowing for alternating firing offset. Only takes effect if weapon has `Burst` set to 1 or undefined. Defaults to false. + +In `rulesmd.ini`: +```ini +[SOMEWEAPON] ; WeaponType +Strafing.Shots=5 ; integer +Strafing.SimulateBurst=false ; bool +``` + ### Custom Radiation Types ![image](_static/images/radtype-01.png) diff --git a/docs/_static/images/strafing-01.gif b/docs/_static/images/strafing-01.gif new file mode 100644 index 0000000000..831f4ba0f6 Binary files /dev/null and b/docs/_static/images/strafing-01.gif differ diff --git a/src/Ext/Aircraft/Body.cpp b/src/Ext/Aircraft/Body.cpp new file mode 100644 index 0000000000..4f364ec43a --- /dev/null +++ b/src/Ext/Aircraft/Body.cpp @@ -0,0 +1,25 @@ +#include "Body.h" +#include + +// TODO: Implement proper extended AircraftClass. + +void AircraftExt::FireBurst(AircraftClass* pThis, AbstractClass* pTarget, int shotNumber = 0) +{ + int weaponIndex = pThis->SelectWeapon(pTarget); + auto weaponType = pThis->GetWeapon(weaponIndex)->WeaponType; + auto pWeaponTypeExt = WeaponTypeExt::ExtMap.Find(weaponType); + + if (weaponType->Burst > 0) + { + while (pThis->CurrentBurstIndex < weaponType->Burst) + { + if (weaponType->Burst < 2 && pWeaponTypeExt->Strafing_SimulateBurst) + pThis->CurrentBurstIndex = shotNumber; + + pThis->Fire(pThis->Target, weaponIndex); + + if (pThis->CurrentBurstIndex == 0) + break; + } + } +} diff --git a/src/Ext/Aircraft/Body.h b/src/Ext/Aircraft/Body.h new file mode 100644 index 0000000000..fd208a4a29 --- /dev/null +++ b/src/Ext/Aircraft/Body.h @@ -0,0 +1,11 @@ +#pragma once +#include + +// TODO: Implement proper extended AircraftClass. + +class AircraftExt +{ +public: + static void FireBurst(AircraftClass* pThis, AbstractClass* pTarget, int shotNumber); +}; + diff --git a/src/Ext/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp new file mode 100644 index 0000000000..37a2cf5997 --- /dev/null +++ b/src/Ext/Aircraft/Hooks.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include + +DEFINE_HOOK(417FF1, AircraftClass_Mission_Attack_StrafeShots, 6) +{ + GET(AircraftClass*, pThis, ESI); + + if (pThis->MissionStatus < (int)AirAttackStatus::FireAtTarget2_Strafe + || pThis->MissionStatus >(int)AirAttackStatus::FireAtTarget5_Strafe) + { + return 0; + } + + int weaponIndex = pThis->SelectWeapon(pThis->Target); + auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pThis->GetWeapon(weaponIndex)->WeaponType); + int fireCount = pThis->MissionStatus - 4; + + if (fireCount > 1 && pWeaponExt->Strafing_Shots < fireCount) + { + if (!pThis->Ammo) + pThis->unknown_bool_6D2 = false; + + pThis->MissionStatus = (int)AirAttackStatus::ReturnToBase; + } + + return 0; +} + +DEFINE_HOOK(418409, AircraftClass_Mission_Attack_FireAtTarget_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + pThis->unknown_bool_6C8 = true; + + AircraftExt::FireBurst(pThis, pThis->Target, 0); + + return 0x418478; +} + +DEFINE_HOOK(4186B6, AircraftClass_Mission_Attack_FireAtTarget2_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + AircraftExt::FireBurst(pThis, pThis->Target, 0); + + return 0x4186D7; +} + +DEFINE_HOOK(418805, AircraftClass_Mission_Attack_FireAtTarget2Strafe_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + AircraftExt::FireBurst(pThis, pThis->Target, 1); + + return 0x418826; +} + +DEFINE_HOOK(418914, AircraftClass_Mission_Attack_FireAtTarget3Strafe_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + AircraftExt::FireBurst(pThis, pThis->Target, 2); + + return 0x418935; +} + +DEFINE_HOOK(418A23, AircraftClass_Mission_Attack_FireAtTarget4Strafe_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + AircraftExt::FireBurst(pThis, pThis->Target, 3); + + return 0x418A44; +} + +DEFINE_HOOK(418B25, AircraftClass_Mission_Attack_FireAtTarget5Strafe_BurstFix, 0) +{ + GET(AircraftClass*, pThis, ESI); + + AircraftExt::FireBurst(pThis, pThis->Target, 4); + + return 0x418B40; +} \ No newline at end of file diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 1a6c20d102..114ed74326 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -3,21 +3,18 @@ template<> const DWORD Extension::Canary = 0x22222222; WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; -void WeaponTypeExt::ExtData::Initialize() -{ - -}; +void WeaponTypeExt::ExtData::Initialize() { } // ============================= // load / save -void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) { +void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) +{ auto pThis = this->OwnerObject(); const char* pSection = pThis->ID; - if (!pINI->GetSection(pSection)) { + if (!pINI->GetSection(pSection)) return; - } INI_EX exINI(pINI); @@ -32,12 +29,18 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) { // RadType if (this->OwnerObject()->RadLevel > 0) + { this->RadType.Read(pINI, pSection, "RadType"); this->Rad_NoOwner.Read(exINI, pSection, "Rad.NoOwner"); + } + + this->Strafing_Shots.Read(exINI, pSection, "Strafing.Shots"); + this->Strafing_SimulateBurst.Read(exINI, pSection, "Strafing.SimulateBurst"); } template -void WeaponTypeExt::ExtData::Serialize(T& Stm) { +void WeaponTypeExt::ExtData::Serialize(T& Stm) +{ Stm .Process(this->DiskLaser_Radius) .Process(this->DiskLaser_Circumference) @@ -45,30 +48,38 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) { .Process(this->Bolt_Disable1) .Process(this->Bolt_Disable2) .Process(this->Bolt_Disable3) + .Process(this->Strafing_Shots) + .Process(this->Strafing_SimulateBurst) ; }; -void WeaponTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) { +void WeaponTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) +{ Extension::LoadFromStream(Stm); this->Serialize(Stm); + if (this->OwnerObject()->RadLevel > 0) this->RadType.LoadFromStream(Stm); } -void WeaponTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) { +void WeaponTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) +{ Extension::SaveToStream(Stm); this->Serialize(Stm); + if (this->OwnerObject()->RadLevel > 0) this->RadType.SaveToStream(Stm); } -bool WeaponTypeExt::LoadGlobals(PhobosStreamReader& Stm) { +bool WeaponTypeExt::LoadGlobals(PhobosStreamReader& Stm) +{ return Stm .Process(nOldCircumference) .Success(); } -bool WeaponTypeExt::SaveGlobals(PhobosStreamWriter& Stm) { +bool WeaponTypeExt::SaveGlobals(PhobosStreamWriter& Stm) +{ return Stm .Process(nOldCircumference) .Success(); @@ -77,8 +88,7 @@ bool WeaponTypeExt::SaveGlobals(PhobosStreamWriter& Stm) { // ============================= // container -WeaponTypeExt::ExtContainer::ExtContainer() : Container("WeaponTypeClass") { -} +WeaponTypeExt::ExtContainer::ExtContainer() : Container("WeaponTypeClass") { } WeaponTypeExt::ExtContainer::~ExtContainer() = default; @@ -90,6 +100,7 @@ DEFINE_HOOK(771EE9, WeaponTypeClass_CTOR, 5) GET(WeaponTypeClass*, pItem, ESI); WeaponTypeExt::ExtMap.FindOrAllocate(pItem); + return 0; } @@ -98,6 +109,7 @@ DEFINE_HOOK(77311D, WeaponTypeClass_SDDTOR, 6) GET(WeaponTypeClass*, pItem, ESI); WeaponTypeExt::ExtMap.Remove(pItem); + return 0; } @@ -115,12 +127,14 @@ DEFINE_HOOK(772CD0, WeaponTypeClass_SaveLoad_Prefix, 7) DEFINE_HOOK(772EA6, WeaponTypeClass_Load_Suffix, 6) { WeaponTypeExt::ExtMap.LoadStatic(); + return 0; } DEFINE_HOOK(772F8C, WeaponTypeClass_Save, 5) { WeaponTypeExt::ExtMap.SaveStatic(); + return 0; } @@ -132,5 +146,6 @@ DEFINE_HOOK(7729B0, WeaponTypeClass_LoadFromINI, 5) GET_STACK(CCINIClass*, pINI, 0xE4); WeaponTypeExt::ExtMap.LoadFromINI(pItem, pINI); + return 0; } diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index bb0d2e3e0a..0b5ca5448e 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -23,7 +23,9 @@ class WeaponTypeExt Valueable Bolt_Disable1; Valueable Bolt_Disable2; Valueable Bolt_Disable3; - + Valueable Strafing_Shots; + Valueable Strafing_SimulateBurst; + ExtData(WeaponTypeClass* OwnerObject) : Extension(OwnerObject) ,DiskLaser_Radius(38.2) ,DiskLaser_Circumference(240) @@ -32,6 +34,8 @@ class WeaponTypeExt ,Bolt_Disable1(false) ,Bolt_Disable2(false) ,Bolt_Disable3(false) + ,Strafing_Shots(5) + ,Strafing_SimulateBurst(false) { } virtual ~ExtData() = default; diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 1026d2b40a..7f98f61e4d 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -34,6 +34,21 @@ #include "./../Phobos.h" +enum class AirAttackStatus +{ + ValidateAZ = 0, + PickAttackLocation = 1, + TakeOff = 2, + FlyToPosition = 3, + FireAtTarget = 4, + FireAtTarget2 = 5, + FireAtTarget2_Strafe = 6, + FireAtTarget3_Strafe = 7, + FireAtTarget4_Strafe = 8, + FireAtTarget5_Strafe = 9, + ReturnToBase = 10 +}; + enum class SuperWeaponAITargetingMode { None = 0, Nuke = 1,