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;