Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ This page lists all the individual contributions to the project by their author.
- Fix an issue where units recruited by a team with `AreTeamMembersRecruitable=false` cannot be recruited even if they have been liberated by that team
- Global default value for `DefaultToGuardArea`
- Weapon range finding in cylinder
- Group retaliate
- **solar-III (凤九歌)**
- Target scanning delay customization (documentation)
- Skip target scanning function calling for unarmed technos (documentation)
Expand Down
29 changes: 29 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,7 @@ Note that this logic is used for [Passenger](https://modenc.renegadeprojects.com
```

### Initial spawns number

- It is now possible to set the initial amount of spawnees for a spawner, instead of always being filled. Won't work if it's larger than `SpawnsNumber`.

In `rulesmd.ini`:
Expand All @@ -1753,6 +1754,34 @@ In `rulesmd.ini`:
InitialSpawnsNumber= ; integer
```

### Group retaliate

- In vanilla, only the unit itself will counter - attack when it takes damage. This often makes units stupidly lured away one by one and eliminated.
- Now you can make units "seek support from nearby friendly units" when they take damage. They will retaliate the attacker together with the victim.
- `GroupRetaliate.AllowAI` and `GroupRetaliate.AllowPlayer` control whether this effect applies to AI units and player units respectively.
- The attacker's threat must be higher than the `GroupRetaliate.ThreatThreshold` of the unit's current target to trigger retaliation.
- `GroupRetaliate.GroupRange` controls the range within which the victim can call for friendly forces.
- `GroupRetaliate.TraceExtraRange` controls the distance for retaliating against the attacker. When the attacker is farther from the unit than the unit's guard range + TraceExtraRange, retaliation will not be triggered.
- An additional option `DisableVanillaRetaliateBehavior` is provided to turn off the vanilla retaliation behavior.

In `rulesmd.ini`:
```ini
[CombatDamage]
GroupRetaliate.AllowAI=false ; boolean
GroupRetaliate.AllowPlayer=false ; boolean
GroupRetaliate.ThreatThreshold=1000.0 ; float, range in cell
GroupRetaliate.GroupRange=7.0 ; float, range in cell
GroupRetaliate.TraceExtraRange=20.0 ; float, range in cell
DisableVanillaRetaliateBehavior=false ; boolean

[SOMETECHNO] ; TechnoType
GroupRetaliate.GroupRange= ; float, range in cell, default to [General] -> GroupRetaliate.GroupRange
```

```{note}
The unit must be able to trigger the vanilla retaliation behavior in order to trigger group retaliate.
```

### Initial strength for TechnoTypes and cloned infantry

![image](_static/images/initialstrength.cloning-01.png)
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ New:
- Option to scale `PowerSurplus` setting if enabled to current power drain with `PowerSurplus.ScaleToDrainAmount` (by Starkku)
- Global default value for `DefaultToGuardArea` (by TaranDahl)
- [Weapon range finding in cylinder](New-or-Enhanced-Logics.md#range-finding-in-cylinder) (by TaranDahl)
- [Group retaliate](New-or-Enhanced-Logics.md#group-retaliate) (by TaranDahl)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
18 changes: 17 additions & 1 deletion src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "Body.h"
#include "Body.h"

#include <Ext/TechnoType/Body.h>
#include <New/Type/RadTypeClass.h>
Expand Down Expand Up @@ -364,6 +364,15 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)

this->CylinderRangefinding.Read(exINI, GameStrings::General, "CylinderRangefinding");

this->GroupRetaliate_AllowAI.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.AllowAI");
this->GroupRetaliate_AllowPlayer.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.AllowPlayer");
this->GroupRetaliate_ThreatThreshold.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.ThreatThreshold");
this->GroupRetaliate_GroupRange.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.GroupRange");
this->GroupRetaliate_TraceExtraRange.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.TraceExtraRange");
this->GroupRetaliate_Delay.Read(exINI, GameStrings::CombatDamage, "GroupRetaliate.Delay");

this->DisableVanillaRetaliateBehavior.Read(exINI, GameStrings::CombatDamage, "DisableVanillaRetaliateBehavior");

// Section AITargetTypes
int itemsCount = pINI->GetKeyCount("AITargetTypes");
for (int i = 0; i < itemsCount; ++i)
Expand Down Expand Up @@ -663,6 +672,13 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->AIParadropMission)
.Process(this->DefaultToGuardArea)
.Process(this->CylinderRangefinding)
.Process(this->GroupRetaliate_AllowAI)
.Process(this->GroupRetaliate_AllowPlayer)
.Process(this->GroupRetaliate_ThreatThreshold)
.Process(this->GroupRetaliate_GroupRange)
.Process(this->GroupRetaliate_TraceExtraRange)
.Process(this->GroupRetaliate_Delay)
.Process(this->DisableVanillaRetaliateBehavior)
;
}

Expand Down
16 changes: 16 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ class RulesExt
Valueable<bool> DefaultToGuardArea;

Valueable<bool> CylinderRangefinding;
Valueable<bool> GroupRetaliate_AllowAI;
Valueable<bool> GroupRetaliate_AllowPlayer;
Valueable<double> GroupRetaliate_ThreatThreshold;
Valueable<Leptons> GroupRetaliate_GroupRange;
Valueable<Leptons> GroupRetaliate_TraceExtraRange;
Valueable<int> GroupRetaliate_Delay;

Valueable<bool> DisableVanillaRetaliateBehavior;

ExtData(RulesClass* OwnerObject) : Extension<RulesClass>(OwnerObject)
, Storage_TiberiumIndex { -1 }
Expand Down Expand Up @@ -570,6 +578,14 @@ class RulesExt
, DefaultToGuardArea { false }

, CylinderRangefinding { false }
, GroupRetaliate_AllowAI { false }
, GroupRetaliate_AllowPlayer { false }
, GroupRetaliate_ThreatThreshold { 1000.0 }
, GroupRetaliate_GroupRange { Leptons(7 * Unsorted::LeptonsPerCell) }
, GroupRetaliate_TraceExtraRange { Leptons(20 * Unsorted::LeptonsPerCell) }
, GroupRetaliate_Delay { 15 }

, DisableVanillaRetaliateBehavior { false }
{ }

virtual ~ExtData() = default;
Expand Down
146 changes: 146 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <Ext/WeaponType/Body.h>

#include <Utilities/AresFunctions.h>
#include <Utilities/Helpers.Alex.h>

TechnoExt::ExtContainer TechnoExt::ExtMap;
UnitClass* TechnoExt::Deployer = nullptr;
Expand Down Expand Up @@ -982,6 +983,150 @@ bool TechnoExt::EjectSurvivor(FootClass* pSurvivor, CoordStruct coords, bool sel
return true;
}

void TechnoExt::ApplyGroupRetaliate(TechnoClass* pThis, ObjectClass* pAttacker, WarheadTypeClass* pWH)
{
auto range = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->GroupRetaliate_GroupRange.Get(RulesExt::Global()->GroupRetaliate_GroupRange);

if (range <= 0)
return;

auto canApproachTarget = [](TechnoClass* pTechno) -> bool
{
if ((pTechno->AbstractFlags & AbstractFlags::Foot) == AbstractFlags::None)
return false;

auto mission = pTechno->GetCurrentMission();
if (mission == Mission::Sticky)
return false;

if (pTechno->DrainTarget)
return false;

auto pUnit = abstract_cast<UnitClass*>(pTechno);
if (pUnit && TechnoExt::CannotMove(pUnit))
return false;

if (!pTechno->GetTechnoType()->CanApproachTarget)
return false;

if (pTechno->BunkerLinkedItem)
return false;

if (pTechno->InOpenToppedTransport)
return false;

return true;
};
auto missionAllowAreaSearch = [](Mission mission)
{
switch (mission)
{
case Mission::Patrol:
case Mission::Hunt:
case Mission::Area_Guard:
case Mission::Rescue:
return true;
default :
return false;
}
};
auto missionAllowRangeSearch = [](Mission mission)
{
switch (mission)
{
case Mission::Move:
case Mission::Guard:
case Mission::Harvest:
return true;
default:
return false;
}
};
auto canRespondGroupRetaliate = [pThis, pAttacker, pWH, range, canApproachTarget, missionAllowAreaSearch, missionAllowRangeSearch](TechnoClass* pTechno) -> bool
{
// Must be ally
if (!pTechno->Owner->IsAlliedWith(pThis))
return false;

// Skip if same target
if (pTechno->Target == pAttacker)
return false;

// Check timer
if (!TechnoExt::ExtMap.Find(pTechno)->GroupRetaliateTimer.Expired())
return false;

// IsAI chcek
if (pTechno->Owner->IsControlledByHuman())
{
if (!RulesExt::Global()->GroupRetaliate_AllowPlayer)
return false;
}
else
{
if (!RulesExt::Global()->GroupRetaliate_AllowAI)
return false;
}

// Has less important target
if (auto pTarget = pTechno->Target)
{
auto attackerThreat = pTechno->ThreatCoeffients(pAttacker, &CoordStruct::Empty);
auto currentThreat = (pTechno->Target->AbstractFlags & AbstractFlags::Object) != AbstractFlags::None ? pTechno->ThreatCoeffients(static_cast<ObjectClass*>(pTechno->Target), &CoordStruct::Empty) : 0.0;

if (attackerThreat - currentThreat < RulesExt::Global()->GroupRetaliate_ThreatThreshold)
return false;
}

// Must check mission
bool closeEnough = pTechno->IsCloseEnoughToAttack(pAttacker);
auto mission = pTechno->GetCurrentMission();
bool attackMove = pTechno->MegaMissionIsAttackMove() && pTechno->InAuxiliarySearchRange(pAttacker);
bool canApproach = missionAllowAreaSearch(mission) && canApproachTarget(pTechno); // TODO : && InAreaStrayRange
bool canRangeFire = missionAllowRangeSearch(mission) && closeEnough;

if (!attackMove && !canApproach && !canRangeFire)
return false;

// Check simple can retaliate
if (!pTechno->CanRetaliateToAttacker(pAttacker, pWH))
return false;

// In search range
if (pThis->DistanceFrom(pTechno) > range)
return false;

// Attacker not too far
if (pAttacker->DistanceFrom(pTechno) > (int)RulesExt::Global()->GroupRetaliate_TraceExtraRange.Get() + pTechno->GetGuardRange(0))
return false;

return true;
};
auto callForHelp = [pThis, pAttacker, pWH, range, missionAllowRangeSearch](TechnoClass* pTechno) -> void
{
TechnoExt::ExtMap.Find(pTechno)->GroupRetaliateTimer.Start(RulesExt::Global()->GroupRetaliate_Delay);
pTechno->SetTarget(pAttacker);

if (pTechno->MegaMissionIsAttackMove())
{
pTechno->QueueMission(Mission::Attack, true);
//pTechno->SetDestination(nullptr, true);
((FootClass*)pTechno)->HaveAttackMoveTarget = true;
}
else if (missionAllowRangeSearch(pTechno->GetCurrentMission()))
{
pTechno->ShouldLoseTargetNow = true;
}
};

auto list = Helpers::Alex::getCellSpreadItems(pThis->GetCoords(), (double)range / Unsorted::LeptonsPerCell, true);
for (auto techno : list)
{
if (canRespondGroupRetaliate(techno))
callForHelp(techno);
}
}

// =============================
// load / save

Expand Down Expand Up @@ -1053,6 +1198,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->SpecialTracked)
.Process(this->FallingDownTracked)
.Process(this->JumpjetStraightAscend)
.Process(this->GroupRetaliateTimer)
;
}

Expand Down
5 changes: 5 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class TechnoExt

bool JumpjetStraightAscend; // Is set to true jumpjet units will ascend straight and do not adjust rotation or position during it.

CDTimerClass GroupRetaliateTimer;

ExtData(TechnoClass* OwnerObject) : Extension<TechnoClass>(OwnerObject)
, TypeExtData { nullptr }
, Shield {}
Expand Down Expand Up @@ -166,6 +168,7 @@ class TechnoExt
, SpecialTracked { false }
, FallingDownTracked { false }
, JumpjetStraightAscend { false }
, GroupRetaliateTimer {}
{ }

void OnEarlyUpdate();
Expand Down Expand Up @@ -311,4 +314,6 @@ class TechnoExt
static void ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH);
static void ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH);
static bool MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* const pTarget, WeaponTypeClass* const pWeaponType);

static void ApplyGroupRetaliate(TechnoClass* pThis, ObjectClass* pAttacker, WarheadTypeClass* pWH);
};
38 changes: 38 additions & 0 deletions src/Ext/Techno/Hooks.ReceiveDamage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,41 @@ DEFINE_HOOK(0x701CFC, TechnoClass_ReceiveDamage_AllowBerzerkOnAllies, 0x5)
enum { IgnoreOwnerCheckFailed = 0x701D0B };
return RulesExt::Global()->AllowBerzerkOnAllies ? IgnoreOwnerCheckFailed : 0;
}

#pragma region GroupRetaliate

namespace GroupRetaliateContext
{
bool Processing = false;
}

DEFINE_HOOK(0x702A31, TechnoClass_ReceiveDamage_GroupRetaliate, 0x7)
{
if (!RulesExt::Global()->GroupRetaliate_AllowAI && !RulesExt::Global()->GroupRetaliate_AllowPlayer)
return 0;

GET_STACK(WarheadTypeClass*, pWH, STACK_OFFSET(0xC4, 0xC));
GET_STACK(ObjectClass*, pAttacker, STACK_OFFSET(0xC4, 0x10));
GET(TechnoClass*, pThis, ESI);
GroupRetaliateContext::Processing = true;
if (pAttacker)
TechnoExt::ApplyGroupRetaliate(pThis, pAttacker, pWH);
GroupRetaliateContext::Processing = false;
return 0;
}

// Skip mission check in vanilla checker. We'll check it by ourselves.
DEFINE_HOOK(0x708A13, TechnoClass_CanRetaliateToAttacker_GroupRetaliate1, 0x6)
{
return GroupRetaliateContext::Processing ? 0x708A2C : 0;
}

DEFINE_HOOK(0x702A58, TechnoClass_ReceiveDamage_DisableVanilla, 0x5)
{
enum { SkipGameCode = 0x702B47 };
GET(TechnoClass*, pThis, ESI);
bool canGroupRetaliate = pThis->Owner->IsControlledByHuman() ? RulesExt::Global()->GroupRetaliate_AllowPlayer : RulesExt::Global()->GroupRetaliate_AllowAI;
return RulesExt::Global()->DisableVanillaRetaliateBehavior && canGroupRetaliate ? SkipGameCode : 0;
}

#pragma endregion
6 changes: 5 additions & 1 deletion src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "Body.h"
#include "Body.h"

#include <JumpjetLocomotionClass.h>

Expand Down Expand Up @@ -1150,6 +1150,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->ParadropMission.Read(exINI, pSection, "ParadropMission");
this->AIParadropMission.Read(exINI, pSection, "AIParadropMission");

this->GroupRetaliate_GroupRange.Read(exINI, pSection, "GroupRetaliate.GroupRange");

// Ares 0.2
this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius");

Expand Down Expand Up @@ -1851,6 +1853,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)

.Process(this->ParadropMission)
.Process(this->AIParadropMission)

.Process(this->GroupRetaliate_GroupRange)
;
}
void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
Expand Down
Loading