Skip to content
Open
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 @@ -289,6 +289,7 @@ This page lists all the individual contributions to the project by their author.
- Guard range customizations
- Wall overlay unit sell exploit fix
- Fix vehicles disguised as trees incorrectly displaying veterancy insignia when they shouldn't
- AI trigger condition type extension
- **Morton (MortonPL)**:
- `XDrawOffset` for animations
- Shield passthrough & absorption
Expand Down
3 changes: 3 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\Ext\AITriggerType\Body.cpp" />
<ClCompile Include="src\Ext\AITriggerType\Hooks.cpp" />
<ClCompile Include="src\Ext\Cell\Hooks.cpp" />
<ClCompile Include="src\Ext\Event\Body.cpp" />
<ClCompile Include="src\Ext\Infantry\Hooks.cpp" />
Expand Down Expand Up @@ -219,6 +221,7 @@
<ClCompile Include="YRpp\StaticInits.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\Ext\AITriggerType\Body.h" />
<ClInclude Include="src\Ext\EBolt\Body.h" />
<ClInclude Include="src\Ext\Event\Body.h" />
<ClInclude Include="src\New\Entity\Ares\RadarJammerClass.h" />
Expand Down
2 changes: 1 addition & 1 deletion YRpp
Submodule YRpp updated 1 files
+11 −1 AITriggerTypeClass.h
19 changes: 19 additions & 0 deletions docs/AI-Scripting-and-Mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,25 @@ x=i,n ; where 18048 <= i <= 18071, n is made up of two parts, the lo

This category is empty for now.

## AI Trigger Condition Types

Phobos adds new AI trigger condition types. AI trigger's condition type is defined as the number in fifth comma-separated parameter in `AITriggerType` declaration.

F.ex
```ini
ID=Name,Team1,OwnerHouse,TechLevel,ConditionType,ConditionObject,Comparator,StartingWeight,MinimumWeight,MaximumWeight,IsForSkirmish,unused,Side,IsBaseDefense,Team2,EnabledInE,EnabledInM,EnabledInH
```

Some condition types operate on or with `ConditionObject` and `Comperator` as well. More information on defining AITriggerTypes can be found on [ModEnc](https://modenc.renegadeprojects.com/AITriggerTypes).

### `8` Amount of Capturable Tech Buildings Exists

Trigger can spring if a number of `Capturable=true` + `NeedsEngineer=true` buildings owned in total by all houses the trigger owner is not allied with satisfies `Comparator`.

### `9` Amount of Bridge Repair Huts Exists

Trigger can spring if a number of `BridgeRepairHut=true` buildings linked to broken bridges owned by first house belonging to `Civilian` side satisfies `Comparator`.

## Trigger Actions

### `500` Save Game
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ New:
- Allow jumpjet climbing ignore building height (by TaranDahl)
- [Allow draw SuperWeapon timer as percentage](User-Interface.md#allow-draw-superweapon-timer-as-percentage) (by NetsuNegi)
- Customize particle system of parasite logic (by NetsuNegi)
- [New AITriggerType conditions for tech buildings & bridge repair huts](AI-Scripting-and-Mapping.md#ai-trigger-condition-types) (by Starkku)

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

AITriggerTypeExt::ExtContainer AITriggerTypeExt::ExtMap;

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

void AITriggerTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
{
// Nothing yet
}

void AITriggerTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
{
// Nothing yet
}

// container

AITriggerTypeExt::ExtContainer::ExtContainer() : Container("AITriggerTypeClass")
{ }

AITriggerTypeExt::ExtContainer::~ExtContainer() = default;

int AITriggerTypeExt::CheckConditions(AITriggerTypeClass* pThis, HouseClass* pOwner, HouseClass* pEnemy)
{
int condition = (int)pThis->ConditionType;

if (condition < -1) // Invalid, bail out early.
return 0;
else if (condition < (int)PhobosAITriggerConditions::NumberOfTechBuildingsExist) // Not Phobos condition
return -1;

bool success = false;

switch ((PhobosAITriggerConditions)condition)
{
case PhobosAITriggerConditions::NumberOfTechBuildingsExist:
success = AITriggerTypeExt::NumberOfTechBuildingsExist(pThis, pOwner);
break;
case PhobosAITriggerConditions::NumberOfBridgeRepairHutsExist:
success = AITriggerTypeExt::NumberOfBridgeRepairHutsExist(pThis);
break;
}

return success;
}

bool AITriggerTypeExt::GetComparatorResult(int operand1, AITriggerConditionComparatorType operatorType, int operand2)
{
switch (operatorType)
{
case AITriggerConditionComparatorType::Less:
return operand1 < operand2;
break;
case AITriggerConditionComparatorType::LessOrEqual:
return operand1 <= operand2;
break;
case AITriggerConditionComparatorType::Equal:
return operand1 == operand2;
break;
case AITriggerConditionComparatorType::GreaterOrEqual:
return operand1 >= operand2;
break;
case AITriggerConditionComparatorType::Greater:
return operand1 > operand2;
break;
case AITriggerConditionComparatorType::NotEqual:
return operand1 != operand2;
break;
default:
return false;
break;
}
}

bool AITriggerTypeExt::NumberOfTechBuildingsExist(AITriggerTypeClass* pThis, HouseClass* pOwner)
{
int count = 0;

for (auto const pHouse : HouseClass::Array)
{
if (pHouse->IsAlliedWith(pOwner))
continue;

// Could possibly be optimized with bespoke tracking but
// it didn't seem to make much of a difference in testing.
for (auto const pBuilding : pHouse->Buildings)
{
if (!pBuilding->IsAlive || pBuilding->InLimbo)
continue;

auto const pType = pBuilding->Type;

if (pType->NeedsEngineer && pType->Capturable)
count++;
}
}

return AITriggerTypeExt::GetComparatorResult(count, pThis->Conditions[0].ComparatorType, pThis->Conditions[0].ComparatorOperand);
}

bool AITriggerTypeExt::NumberOfBridgeRepairHutsExist(AITriggerTypeClass* pThis)
{
int count = 0;
auto const pHouse = HouseClass::FindCivilianSide();

for (auto const pBuilding : pHouse->Buildings)
{
if (!pBuilding->IsAlive || pBuilding->InLimbo)
continue;

auto const pType = pBuilding->Type;

if (pType->BridgeRepairHut && MapClass::Instance.IsLinkedBridgeDestroyed(pBuilding->GetMapCoords()))
count++;
}

return AITriggerTypeExt::GetComparatorResult(count, pThis->Conditions[0].ComparatorType, pThis->Conditions[0].ComparatorOperand);
}
50 changes: 50 additions & 0 deletions src/Ext/AITriggerType/Body.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include <AITriggerTypeClass.h>
#include <Utilities/Container.h>

// Vanilla game ones range from -1 to 7, see AITriggerCondition in GeneralDefinitions.h in YRpp.
enum class PhobosAITriggerConditions : unsigned int
{
NumberOfTechBuildingsExist = 8,
NumberOfBridgeRepairHutsExist = 9,
};

class AITriggerTypeExt
{
public:
using base_type = AITriggerTypeClass;

static constexpr DWORD Canary = 0x9B9B9B9B;

class ExtData final : public Extension<AITriggerTypeClass>
{
public:
// Nothing yet

ExtData(AITriggerTypeClass* OwnerObject) : Extension<AITriggerTypeClass>(OwnerObject)
// Nothing yet
{ }

virtual ~ExtData() = default;

virtual void InvalidatePointer(void* ptr, bool bRemoved) override { }

virtual void LoadFromStream(PhobosStreamReader& Stm) override;
virtual void SaveToStream(PhobosStreamWriter& Stm) override;

};

class ExtContainer final : public Container<AITriggerTypeExt>
{
public:
ExtContainer();
~ExtContainer();
};

static ExtContainer ExtMap;

static int CheckConditions(AITriggerTypeClass* pThis, HouseClass* pOwner, HouseClass* pEnemy);
static bool GetComparatorResult(int operand1, AITriggerConditionComparatorType operatorType, int operand2);
static bool NumberOfTechBuildingsExist(AITriggerTypeClass* pThis, HouseClass* pOwner);
static bool NumberOfBridgeRepairHutsExist(AITriggerTypeClass* pThis);
};
26 changes: 26 additions & 0 deletions src/Ext/AITriggerType/Hooks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <Ext/AITriggerType/Body.h>
#include <Helpers/Macro.h>

DEFINE_HOOK(0x41E8FF, AITriggerTypeClass_NewTeam_CheckConditions, 0x9) // ConditionMet() in YRpp
{
enum { ContinueGameChecks = 0x41E908, ReturnFromFunction = 0x41E9E1, Success = 0x41E9EA };

GET(AITriggerTypeClass*, pThis, ESI);
GET(HouseClass*, pOwner, EDI);
GET(HouseClass*, pEnemy, EBX);

int result = AITriggerTypeExt::CheckConditions(pThis, pOwner, pEnemy);

switch (result)
{
case 0:
return ReturnFromFunction;
break;
case 1:
return Success;
break;
default:
return ContinueGameChecks;
break;
}
}