diff --git a/CREDITS.md b/CREDITS.md index 599ca1b29a..a3ca076457 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -609,13 +609,16 @@ This page lists all the individual contributions to the project by their author. - Auto deploy for GI-like infantry - Fix an issue that Ares' Type Conversion not resetting barrel's direction by `FireAngle` - Fix an issue that jumpjets in air can not correctly spawn missiles + - Passive acquire mode - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) - **tyuah8**: - Drive/Jumpjet/Ship/Teleport locomotor did not power on when it is un-piggybacked bugfix - Destroyed unit leaves sensors bugfix -- **Aephiex** - initial fix for Ares academy not working on the initial payloads of vehicles built from a war factory +- **Aephiex** + - initial fix for Ares academy not working on the initial payloads of vehicles built from a war factory + - Passive acquire mode (aggressive mode part) - **Multfinite** - Allow to toggle main exception handler via command line argument `-ExceptionHandler=boolean` - **hejiajun107, Xkein** - Fix a jumpjet crash related to voxel shadow drawing - **Ares developers**: diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 5e5f7ceead..9bc5163b77 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -18,6 +18,8 @@ + + @@ -213,7 +215,9 @@ + + diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b920d5b00e..c4313d2cd8 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1794,6 +1794,32 @@ In `rulesmd.ini`: Promote.IncludeSpawns=false ; boolean ``` +### Passive acquire mode + +- Now you can order the units to enter "Ceasefire Mode" or "Aggressive Mode", just like in RA3. + - In ceasefire mode, units will only attack the targets specified by the player. + - In aggressive mode, units will automatically attack all enemies, including buildings without weapons. + - In normal mode, units will auto-target like usual. +- You can use [two](User-Interface#ceasefire-mode) [hotkeys](User-Interface#aggressive-mode) to switch between these two modes if `[General] -> EnablePassiveAcquireMode` is set to `true`. When you press one of these hotkeys: + - If all selected units are in that mode, they will switch to the normal mode. Otherwise, they will enter that mode. Units that cannot switch modes are not counted. + - If any unit successfully switches its mode, a sound effect will be emitted. This sound can be customized through `[TechnoType] -> Voice(Enter/Exit)(Aggressive/Ceasefire)Mode`. +- You can use `[TechnoType] -> PassiveAcquireMode.Togglable` to specify whether the unit can toggle its mode. +- You can use `[TechnoType] -> PassiveAcquireMode` to specify the unit's initial mode. + +In `rulesmd.ini`: +```ini +[General] +EnablePassiveAcquireMode=false ; boolean + +[SOMETECHNO] ; TechnoType +PassiveAcquireMode=Normal ; passive acquire mode, Normal / Aggressive / Ceasefire +PassiveAcquireMode.Togglable=true ; boolean +VoiceEnterAggressiveMode= ; Sound entry, default to VoiceAttack or VoiceMove +VoiceExitAggressiveMode= ; Sound entry, default to VoiceMove or VoiceSelect +VoiceEnterCeasefireMode= ; Sound entry, default to VoiceSelect or VoiceMove +VoiceExitCeasefireMode= ; Sound entry, default to VoiceAttack or VoiceMove +``` + ### Promotion animation - You can now specify an animation on the unit or structure promotion. diff --git a/docs/User-Interface.md b/docs/User-Interface.md index 7d583c76bf..8e1c570132 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -479,6 +479,16 @@ For this command to work in multiplayer - you need to use a version of [YRpp spa - These vanilla CSF entries will be used: `TXT_SAVING_GAME`, `TXT_GAME_WAS_SAVED` and `TXT_ERROR_SAVING_GAME`. - The save should be looks like `Allied Mission 25: Esther's Money - QuickSaved`. +### `[ ]` Ceasefire Mode + +- Order the selected units to enter or exit the ceasefire mode. See [this](New-or-Enhanced-Logics#passive-acquire-mode) for details. +- For localization add `TXT_CEASEFIRE_MODE`, `TXT_CEASEFIRE_MODE_DESC`, `MSG:CEASEFIRE_MODE_ON` and `MSG:CEASEFIRE_MODE_OFF` into your `.csf` file. + +### `[ ]` Aggressive Mode + +- Order the selected units to enter or exit the aggressive mode. See [this](New-or-Enhanced-Logics#passive-acquire-mode) for details. +- For localization add `TXT_AGGRESSIVE_MODE`, `TXT_AGGRESSIVE_MODE_DESC`, `MSG:AGGRESSIVE_MODE_ON` and `MSG:AGGRESSIVE_MODE_OFF` into your `.csf` file. + ## Loading screen - PCX files can now be used as loadscreen images. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 432a86fe92..a33cdec955 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -443,6 +443,7 @@ New: - [Ammo-based deploy customizations for vehicles expanded to non-IsSimpleDeployer deploy functions](New-or-Enhanced-Logics.md#automatic-deploy-and-blocking-deploying-based-on-ammo) (by Starkku) - Randomized anims for several behaviors (by Ollerus) - Shield respawn animation and weapon (by Ollerus) +- Passive acquire mode (by TaranDahl & Aephiex) 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/Commands/Commands.cpp b/src/Commands/Commands.cpp index 8d9d2611a1..81144fa223 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -12,6 +12,14 @@ #include "SaveVariablesToFile.h" #include "ToggleSWSidebar.h" #include "FireTacticalSW.h" +#include "PassiveAcquireMode.h" + +#include +#include +#include +#include + +#include #include DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) @@ -22,6 +30,8 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) MakeCommand(); MakeCommand(); MakeCommand(); + MakeCommand(); + MakeCommand(); MakeCommand(); if (Phobos::Config::SuperWeaponSidebarCommands) diff --git a/src/Commands/PassiveAcquireMode.cpp b/src/Commands/PassiveAcquireMode.cpp new file mode 100644 index 0000000000..827e03d0d4 --- /dev/null +++ b/src/Commands/PassiveAcquireMode.cpp @@ -0,0 +1,210 @@ +#include "PassiveAcquireMode.h" + +#include "Ext/Techno/Body.h" +#include + +const char* AggressiveModeClass::GetName() const +{ + return "AggressiveMode"; +} + +const wchar_t* AggressiveModeClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_AGGRESSIVE_MODE", L"Aggressive Mode"); +} + +const wchar_t* AggressiveModeClass::GetUICategory() const +{ + return CATEGORY_CONTROL; +} + +const wchar_t* AggressiveModeClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_AGGRESSIVE_MODE_DESC", L"Aggressive Mode"); +} + +void AggressiveModeClass::Execute(WWKey eInput) const +{ + std::vector TechnoVectorAggressive; + std::vector TechnoVectorNonAggressive; + + // Get current selected units. + // If all selected units are at Aggressive mode, we should cancel their Aggressive mode. + // Otherwise, we should turn them into Aggressive mode. + bool isAnySelectedUnitTogglable = false; + bool isAllSelectedUnitAggressiveMode = true; + + auto processATechno = [&](TechnoClass* pTechno) + { + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + + // If not togglable then exclude it from the iteration. + if (!pTechnoExt->CanTogglePassiveAcquireMode()) + return; + + isAnySelectedUnitTogglable = true; + + if (pTechnoExt->GetPassiveAcquireMode() == PassiveAcquireMode::Aggressive) + { + TechnoVectorAggressive.push_back(pTechno); + } + else + { + isAllSelectedUnitAggressiveMode = false; + TechnoVectorNonAggressive.push_back(pTechno); + } + return; + }; + + for (const auto& pUnit : ObjectClass::CurrentObjects) + { + // try to cast to TechnoClass + TechnoClass* pTechno = abstract_cast(pUnit); + + // if not a techno or is in berserk or is not controlled by the local player then ignore it + if (!pTechno || pTechno->Berzerk || !pTechno->Owner->IsControlledByCurrentPlayer()) + continue; + + processATechno(pTechno); + + if (auto pPassenger = pTechno->Passengers.GetFirstPassenger()) + { + for (; pPassenger; pPassenger = abstract_cast(pPassenger->NextObject)) + processATechno(pPassenger); + } + + if (auto pBuilding = abstract_cast(pTechno)) + { + for (auto pOccupier : pBuilding->Occupants) + processATechno(pOccupier); + } + } + + // If this boolean is false, then none of the selected units are togglable, meaning this hotket doesn't need to do anything. + if (isAnySelectedUnitTogglable) + { + // If all selected units are Aggressive mode, then cancel their Aggressive mode; + // otherwise, make all selected units Aggressive mode. + if (isAllSelectedUnitAggressiveMode) + { + for (const auto& pTechno : TechnoVectorAggressive) + EventExt::RaiseTogglePassiveAcquireMode(pTechno, PassiveAcquireMode::Normal); + + wchar_t buffer[0x100]; + swprintf_s(buffer, GeneralUtils::LoadStringUnlessMissing("MSG:AGGRESSIVE_MODE_OFF", L"%i unit(s) ceased Aggressive Mode."), TechnoVectorAggressive.size()); + MessageListClass::Instance.PrintMessage(buffer); + } + else + { + for (const auto& pTechno : TechnoVectorNonAggressive) + EventExt::RaiseTogglePassiveAcquireMode(pTechno, PassiveAcquireMode::Aggressive); + + wchar_t buffer[0x100]; + swprintf_s(buffer, GeneralUtils::LoadStringUnlessMissing("MSG:AGGRESSIVE_MODE_ON", L"%i unit(s) entered Aggressive Mode."), TechnoVectorNonAggressive.size()); + MessageListClass::Instance.PrintMessage(buffer); + } + } +} + +const char* CeasefireModeClass::GetName() const +{ + return "CeasefireMode"; +} + +const wchar_t* CeasefireModeClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_CEASEFIRE_MODE", L"Ceasefire Mode"); +} + +const wchar_t* CeasefireModeClass::GetUICategory() const +{ + return CATEGORY_CONTROL; +} + +const wchar_t* CeasefireModeClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_CEASEFIRE_MODE_DESC", L"Ceasefire Mode"); +} + +void CeasefireModeClass::Execute(WWKey eInput) const +{ + std::vector TechnoVectorCeasefire; + std::vector TechnoVectorNonCeasefire; + + // Get current selected units. + // If all selected units are at Ceasefire mode, we should cancel their Ceasefire mode. + // Otherwise, we should turn them into Ceasefire mode. + bool isAnySelectedUnitTogglable = false; + bool isAllSelectedUnitCeasefireMode = true; + + auto processATechno = [&](TechnoClass* pTechno) + { + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + + // If not togglable then exclude it from the iteration. + if (!pTechnoExt->CanTogglePassiveAcquireMode()) + return; + + isAnySelectedUnitTogglable = true; + + if (pTechnoExt->GetPassiveAcquireMode() == PassiveAcquireMode::Ceasefire) + { + TechnoVectorCeasefire.push_back(pTechno); + } + else + { + isAllSelectedUnitCeasefireMode = false; + TechnoVectorNonCeasefire.push_back(pTechno); + } + return; + }; + + for (const auto& pUnit : ObjectClass::CurrentObjects) + { + // try to cast to TechnoClass + TechnoClass* pTechno = abstract_cast(pUnit); + + // if not a techno or is in berserk or is not controlled by the local player then ignore it + if (!pTechno || pTechno->Berzerk || !pTechno->Owner->IsControlledByCurrentPlayer()) + continue; + + processATechno(pTechno); + + if (auto pPassenger = pTechno->Passengers.GetFirstPassenger()) + { + for (; pPassenger; pPassenger = abstract_cast(pPassenger->NextObject)) + processATechno(pPassenger); + } + + if (auto pBuilding = abstract_cast(pTechno)) + { + for (auto pOccupier : pBuilding->Occupants) + processATechno(pOccupier); + } + } + + // If this boolean is false, then none of the selected units are togglable, meaning this hotket doesn't need to do anything. + if (isAnySelectedUnitTogglable) + { + // If all selected units are Ceasefire mode, then cancel their Ceasefire mode; + // otherwise, make all selected units Ceasefire mode. + if (isAllSelectedUnitCeasefireMode) + { + for (const auto& pTechno : TechnoVectorCeasefire) + EventExt::RaiseTogglePassiveAcquireMode(pTechno, PassiveAcquireMode::Normal); + + wchar_t buffer[0x100]; + swprintf_s(buffer, GeneralUtils::LoadStringUnlessMissing("MSG:CEASEFIRE_MODE_OFF", L"%i unit(s) ceased Ceasefire Mode."), TechnoVectorCeasefire.size()); + MessageListClass::Instance.PrintMessage(buffer); + } + else + { + for (const auto& pTechno : TechnoVectorNonCeasefire) + EventExt::RaiseTogglePassiveAcquireMode(pTechno, PassiveAcquireMode::Ceasefire); + + wchar_t buffer[0x100]; + swprintf_s(buffer, GeneralUtils::LoadStringUnlessMissing("MSG:CEASEFIRE_MODE_ON", L"%i unit(s) entered Ceasefire Mode."), TechnoVectorNonCeasefire.size()); + MessageListClass::Instance.PrintMessage(buffer); + } + } +} diff --git a/src/Commands/PassiveAcquireMode.h b/src/Commands/PassiveAcquireMode.h new file mode 100644 index 0000000000..51ba94177c --- /dev/null +++ b/src/Commands/PassiveAcquireMode.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Commands.h" + +class AggressiveModeClass : public CommandClass +{ +public: + // CommandClass + virtual const char* GetName() const override; + virtual const wchar_t* GetUIName() const override; + virtual const wchar_t* GetUICategory() const override; + virtual const wchar_t* GetUIDescription() const override; + virtual void Execute(WWKey eInput) const override; +}; + +class CeasefireModeClass : public CommandClass +{ +public: + // CommandClass + virtual const char* GetName() const override; + virtual const wchar_t* GetUIName() const override; + virtual const wchar_t* GetUICategory() const override; + virtual const wchar_t* GetUIDescription() const override; + virtual void Execute(WWKey eInput) const override; +}; diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index acad4d12e9..0cb5b2a33c 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -219,6 +219,8 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Refinery_UseNormalActiveAnim.Read(exArtINI, pArtSection, "Refinery.UseNormalActiveAnim"); + this->AggressiveModeExempt.Read(exINI, pSection, "AggressiveModeExempt"); + // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); if (SuperWeaponTypeClass::Array.Count > 0) @@ -334,6 +336,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->BuildingRepairedSound) .Process(this->Refinery_UseNormalActiveAnim) .Process(this->HasPowerUpAnim) + .Process(this->AggressiveModeExempt) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 9cf41a7af4..dbd38e000d 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -99,6 +99,8 @@ class BuildingTypeExt ValueableVector HasPowerUpAnim; + Valueable AggressiveModeExempt; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -161,6 +163,7 @@ class BuildingTypeExt , BuildingRepairedSound {} , Refinery_UseNormalActiveAnim { false } , HasPowerUpAnim {} + , AggressiveModeExempt{} { } // Ares 0.A functions diff --git a/src/Ext/Event/Body.cpp b/src/Ext/Event/Body.cpp index c4c471c55d..9ecdc9211c 100644 --- a/src/Ext/Event/Body.cpp +++ b/src/Ext/Event/Body.cpp @@ -1,4 +1,4 @@ -/* + #include "Body.h" #include @@ -13,18 +13,43 @@ void EventExt::RespondEvent() { switch (this->Type) { - case EventTypeExt::Sample: - // Place the handler here + case EventTypeExt::TogglePassiveAcquireMode: + this->RespondToTogglePassiveAcquireMode(); break; } } +void EventExt::RaiseTogglePassiveAcquireMode(TechnoClass* pTechno, PassiveAcquireMode mode) +{ + EventExt eventExt {}; + eventExt.Type = EventTypeExt::TogglePassiveAcquireMode; + eventExt.HouseIndex = static_cast(pTechno->Owner->ArrayIndex); + eventExt.Frame = Unsorted::CurrentFrame; + eventExt.TogglePassiveAcquireMode.Who = TargetClass(pTechno); + eventExt.TogglePassiveAcquireMode.Mode = mode; + eventExt.AddEvent(); +} + +void EventExt::RespondToTogglePassiveAcquireMode() +{ + if (const auto pTechno = this->TogglePassiveAcquireMode.Who.As_Techno()) + { + if (pTechno->IsAlive && !pTechno->Berzerk) + { + const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + + if (pTechnoExt->CanTogglePassiveAcquireMode()) + pTechnoExt->TogglePassiveAcquireMode(this->TogglePassiveAcquireMode.Mode); + } + } +} + size_t EventExt::GetDataSize(EventTypeExt type) { switch (type) { - case EventTypeExt::Sample: - return sizeof(EventExt::Sample); + case EventTypeExt::TogglePassiveAcquireMode: + return sizeof(EventExt::TogglePassiveAcquireMode); } return 0; @@ -98,4 +123,3 @@ DEFINE_HOOK(0x64C30E, sub_64BDD0_GetEventSize2, 0x6) return 0; } -*/ diff --git a/src/Ext/Event/Body.h b/src/Ext/Event/Body.h index a9d37294e6..67ad6f6c06 100644 --- a/src/Ext/Event/Body.h +++ b/src/Ext/Event/Body.h @@ -1,18 +1,24 @@ #pragma once -/* + #include #include +#include "HouseClass.h" +#include "TechnoClass.h" +#include "TargetClass.h" + +#include + enum class EventTypeExt : uint8_t { // Vanilla game used Events from 0x00 to 0x2F // CnCNet reserved Events from 0x30 to 0x3F // Ares used Events 0x60 and 0x61 - Sample = 0x40, // Sample event, remove it when Phobos needs its own events + TogglePassiveAcquireMode = 0x81, - FIRST = Sample, - LAST = Sample + FIRST = TogglePassiveAcquireMode, + LAST = TogglePassiveAcquireMode }; #pragma pack(push, 1) @@ -27,15 +33,19 @@ class EventExt { char DataBuffer[104]; - struct Sample + struct TogglePassiveAcquireMode { - char DataBuffer[104]; - } Sample; + TargetClass Who; + PassiveAcquireMode Mode; + } TogglePassiveAcquireMode; }; bool AddEvent(); void RespondEvent(); + static void RaiseTogglePassiveAcquireMode(TechnoClass* pTechno, PassiveAcquireMode mode); + void RespondToTogglePassiveAcquireMode(); + static size_t GetDataSize(EventTypeExt type); static bool IsValidType(EventTypeExt type); }; @@ -43,4 +53,4 @@ class EventExt static_assert(sizeof(EventExt) == 111); static_assert(offsetof(EventExt, DataBuffer) == 7); #pragma pack(pop) -*/ + diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index c30c2e8849..a25b8f372f 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -1,4 +1,4 @@ -#include "Body.h" +#include "Body.h" #include #include #include @@ -313,6 +313,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->InfantryAutoDeploy.Read(exINI, GameStrings::General, "InfantryAutoDeploy"); + this->EnablePassiveAcquireMode.Read(exINI, GameStrings::General, "EnablePassiveAcquireMode"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -577,6 +579,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->AttackMove_StopWhenTargetAcquired) .Process(this->Parasite_GrappleAnim) .Process(this->InfantryAutoDeploy) + .Process(this->EnablePassiveAcquireMode) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 190cd8b2e7..7ac7194b4d 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -225,6 +225,8 @@ class RulesExt Valueable AIAllToHunt; Valueable RepairBaseNodes; + Valueable EnablePassiveAcquireMode; + Valueable WarheadParticleAlphaImageIsLightFlash; Valueable CombatLightDetailLevel; Valueable LightFlashAlphaImageDetailLevel; @@ -435,6 +437,7 @@ class RulesExt , AIFireSaleDelay { 0 } , AIAllToHunt { true } , RepairBaseNodes { false } + , EnablePassiveAcquireMode { false } , WarheadParticleAlphaImageIsLightFlash { false } , CombatLightDetailLevel { 0 } , LightFlashAlphaImageDetailLevel { 0 } diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 5d43b41ccf..2c0294111c 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -802,6 +802,98 @@ void TechnoExt::HandleOnDeployAmmoChange(TechnoClass* pThis, int maxAmmoOverride } } +void TechnoExt::ExtData::InitPassiveAcquireMode() +{ + this->PassiveAquireMode = this->TypeExtData->PassiveAcquireMode.Get(); +} + +PassiveAcquireMode TechnoExt::ExtData::GetPassiveAcquireMode() const +{ + // if this is a passenger then obey the configuration of the transport + if (auto pTransport = this->OwnerObject()->Transporter) + return TechnoExt::ExtMap.Find(pTransport)->GetPassiveAcquireMode(); + + return this->PassiveAquireMode; +} + +void TechnoExt::ExtData::TogglePassiveAcquireMode(PassiveAcquireMode newMode) +{ + auto previousMode = this->PassiveAquireMode; + this->PassiveAquireMode = newMode; + + if (newMode == previousMode) + return; + + const auto pThis = this->OwnerObject(); + const auto pTechnoType = this->TypeExtData->OwnerObject(); + int voiceIndex; + + if (newMode == PassiveAcquireMode::Normal) + { + if (previousMode == PassiveAcquireMode::Ceasefire) + { + voiceIndex = this->TypeExtData->VoiceExitCeasefireMode.Get(); + + if (voiceIndex < 0) + { + const auto& voiceList = pTechnoType->VoiceAttack.Count ? pTechnoType->VoiceAttack : pTechnoType->VoiceMove; + + if (const auto count = voiceList.Count) + voiceIndex = voiceList.GetItem(Randomizer::Global.Random() % count); + } + } + else + { + pThis->SetTarget(nullptr); + voiceIndex = this->TypeExtData->VoiceExitAggressiveMode.Get(); + + if (voiceIndex < 0) + { + const auto& voiceList = pTechnoType->VoiceMove.Count ? pTechnoType->VoiceMove : pTechnoType->VoiceSelect; + + if (const auto count = voiceList.Count) + voiceIndex = voiceList.GetItem(Randomizer::Global.Random() % count); + } + } + } + else if (newMode == PassiveAcquireMode::Ceasefire) + { + pThis->SetTarget(nullptr); + voiceIndex = this->TypeExtData->VoiceEnterCeasefireMode.Get(); + + if (voiceIndex < 0) + { + const auto& voiceList = pTechnoType->VoiceSelect.Count ? pTechnoType->VoiceSelect : pTechnoType->VoiceMove; + + if (const auto count = voiceList.Count) + voiceIndex = voiceList.GetItem(Randomizer::Global.Random() % count); + } + } + else + { + voiceIndex = this->TypeExtData->VoiceEnterAggressiveMode.Get(); + + if (voiceIndex < 0) + { + const auto& voiceList = pTechnoType->VoiceAttack.Count ? pTechnoType->VoiceAttack : pTechnoType->VoiceMove; + + if (const auto count = voiceList.Count) + voiceIndex = voiceList.GetItem(Randomizer::Global.Random() % count); + } + } + + pThis->QueueVoice(voiceIndex); +} + +bool TechnoExt::ExtData::CanTogglePassiveAcquireMode() +{ + if (!RulesExt::Global()->EnablePassiveAcquireMode) + return false; + + return this->TypeExtData->PassiveAcquireMode_Togglable; +} + + // ============================= // load / save @@ -866,6 +958,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->TintIntensityAllies) .Process(this->TintIntensityEnemies) .Process(this->AttackMoveFollowerTempCount) + .Process(this->PassiveAquireMode) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 3734979dd4..3948be8c80 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -1,7 +1,10 @@ -#pragma once +#pragma once #include #include +#include +#include + #include #include #include @@ -94,6 +97,8 @@ class TechnoExt int AttackMoveFollowerTempCount; + PassiveAcquireMode PassiveAquireMode; + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } , Shield {} @@ -153,6 +158,7 @@ class TechnoExt , TintIntensityAllies { 0 } , TintIntensityEnemies { 0 } , AttackMoveFollowerTempCount { 0 } + , PassiveAquireMode{ PassiveAcquireMode::Normal } { } void OnEarlyUpdate(); @@ -196,6 +202,11 @@ class TechnoExt virtual void LoadFromStream(PhobosStreamReader& Stm) override; virtual void SaveToStream(PhobosStreamWriter& Stm) override; + void InitPassiveAcquireMode(); + PassiveAcquireMode GetPassiveAcquireMode() const; + void TogglePassiveAcquireMode(PassiveAcquireMode mode); + bool CanTogglePassiveAcquireMode(); + private: template void Serialize(T& Stm); diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 10daf28aac..8f04c08ac3 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -206,12 +206,25 @@ DEFINE_HOOK(0x6F85AB, TechnoClass_CanAutoTargetObject_AggressiveAttackMove, 0x6) if (!pThis->Owner->IsControlledByHuman()) return CanTarget; - if (!pThis->MegaMissionIsAttackMove()) - return ContinueCheck; + GET(TechnoClass*, pTarget, ESI); - const auto pExt = TechnoExt::ExtMap.Find(pThis); + if (pTarget->WhatAmI() == AbstractType::Building) + { + // Fallback to unmodded behavior if the building is an exempt of aggressive stance. + if (BuildingTypeExt::ExtMap.Find(static_cast(pTarget)->Type)->AggressiveModeExempt) + return ContinueCheck; - return pExt->TypeExtData->AttackMove_Aggressive.Get(RulesExt::Global()->AttackMove_Aggressive) ? CanTarget : ContinueCheck; + if (TechnoExt::ExtMap.Find(pThis)->GetPassiveAcquireMode() == PassiveAcquireMode::Aggressive) + return CanTarget; + } + + if (pThis->MegaMissionIsAttackMove()) + { + if (TechnoExt::ExtMap.Find(pThis)->TypeExtData->AttackMove_Aggressive.Get(RulesExt::Global()->AttackMove_Aggressive)) + return CanTarget; + } + + return ContinueCheck; } #pragma endregion @@ -376,3 +389,23 @@ Action __fastcall InfantryClass__WhatAction_Wrapper(InfantryClass* pThis, void* DEFINE_FUNCTION_JUMP(VTABLE, 0x7EB0CC, InfantryClass__WhatAction_Wrapper) #pragma endregion + +#pragma region PassiveAcquireMode + +DEFINE_HOOK(0x6F8E1F, TechnoClass_SelectAutoTarget_CeasefireMode, 0x6) +{ + GET(TechnoTypeClass*, pType, EAX); + GET(TechnoClass*, pThis, ESI); + R->CL(pType->NoAutoFire || (TechnoExt::ExtMap.Find(pThis)->GetPassiveAcquireMode()) == PassiveAcquireMode::Ceasefire); + return R->Origin() + 0x6; +} + +DEFINE_HOOK(0x7087DD, TechnoClass_CanRetaliateToAttacker_CeasefireMode, 0x6) +{ + GET(TechnoTypeClass*, pType, EAX); + GET(TechnoClass*, pThis, ESI); + R->CL(pType->CanRetaliate && (TechnoExt::ExtMap.Find(pThis)->GetPassiveAcquireMode() != PassiveAcquireMode::Ceasefire)); + return R->Origin() + 0x6; +} + +#pragma endregion diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 75007b1560..c3bec9cc99 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -231,6 +231,8 @@ DEFINE_HOOK(0x6F42F7, TechnoClass_Init, 0x2) pThis->TargetingTimer.Start(ScenarioClass::Instance->Random.RandomRanged(0, 15)); } + pExt->InitPassiveAcquireMode(); + return 0; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 306016e501..f97c830601 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -1,4 +1,4 @@ -#include "Body.h" +#include "Body.h" #include #include @@ -956,7 +956,14 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AttackMove_PursuitTarget.Read(exINI, pSection, "AttackMove.PursuitTarget"); this->InfantryAutoDeploy.Read(exINI, pSection, "InfantryAutoDeploy"); - + + this->PassiveAcquireMode.Read(exINI, pSection, "PassiveAcquireMode"); + this->PassiveAcquireMode_Togglable.Read(exINI, pSection, "PassiveAcquireMode.Togglable"); + this->VoiceEnterAggressiveMode.Read(exINI, pSection, "VoiceEnterAggressiveMode"); + this->VoiceExitAggressiveMode.Read(exINI, pSection, "VoiceExitAggressiveMode"); + this->VoiceEnterCeasefireMode.Read(exINI, pSection, "VoiceEnterCeasefireMode"); + this->VoiceExitCeasefireMode.Read(exINI, pSection, "VoiceExitCeasefireMode"); + // Ares 0.2 this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius"); @@ -1340,6 +1347,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->AutoFire) .Process(this->AutoFire_TargetSelf) + .Process(this->NoSecondaryWeaponFallback) .Process(this->NoSecondaryWeaponFallback_AllowAA) .Process(this->NoAmmoWeapon) @@ -1599,6 +1607,14 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->InfantryAutoDeploy) .Process(this->TurretResponse) + + .Process(this->PassiveAcquireMode) + .Process(this->PassiveAcquireMode_Togglable) + .Process(this->VoiceEnterAggressiveMode) + .Process(this->VoiceExitAggressiveMode) + .Process(this->VoiceEnterCeasefireMode) + .Process(this->VoiceExitCeasefireMode) + ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) @@ -1702,3 +1718,34 @@ DEFINE_HOOK(0x747E90, UnitTypeClass_LoadFromINI, 0x5) return 0; } + +namespace detail +{ + template <> + inline bool read(PassiveAcquireMode& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + auto str = parser.value(); + if (_strcmpi(str, "Normal") == 0) + { + value = PassiveAcquireMode::Normal; + } + else if (_strcmpi(str, "Aggressive") == 0) + { + value = PassiveAcquireMode::Aggressive; + } + else if (_strcmpi(str, "Ceasefire") == 0) + { + value = PassiveAcquireMode::Ceasefire; + } + else + { + Debug::INIParseFailed(pSection, pKey, str, "Expected passive acquire mode."); + return false; + } + return true; + } + return false; + } +} diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index b603ab0aab..ffbea6ef12 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -16,6 +16,13 @@ #include #include +enum class PassiveAcquireMode : BYTE +{ + Normal = 0, + Aggressive = 1, + Ceasefire = 2, +}; + class Matrix3D; class ParticleSystemTypeClass; class TechnoTypeExt @@ -425,6 +432,13 @@ class TechnoTypeExt Nullable TurretResponse; + Valueable PassiveAcquireMode; + Valueable PassiveAcquireMode_Togglable; + ValueableIdx VoiceEnterAggressiveMode; + ValueableIdx VoiceExitAggressiveMode; + ValueableIdx VoiceEnterCeasefireMode; + ValueableIdx VoiceExitCeasefireMode; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -799,6 +813,14 @@ class TechnoTypeExt , InfantryAutoDeploy {} , TurretResponse {} + + , PassiveAcquireMode { PassiveAcquireMode::Normal } + , PassiveAcquireMode_Togglable { true } + , VoiceEnterAggressiveMode { -1 } + , VoiceExitAggressiveMode { -1 } + , VoiceEnterCeasefireMode { -1 } + , VoiceExitCeasefireMode { -1 } + { } virtual ~ExtData() = default;