From 420848e87ff37b9fa68df79dba9a041ce973f5ee Mon Sep 17 00:00:00 2001 From: Starkku Date: Tue, 24 Feb 2026 18:10:56 +0200 Subject: [PATCH] Add additional AITriggerType conditions --- CREDITS.md | 1 + Phobos.vcxproj | 3 + YRpp | 2 +- docs/AI-Scripting-and-Mapping.md | 19 +++++ docs/Whats-New.md | 1 + src/Ext/AITriggerType/Body.cpp | 120 +++++++++++++++++++++++++++++++ src/Ext/AITriggerType/Body.h | 50 +++++++++++++ src/Ext/AITriggerType/Hooks.cpp | 26 +++++++ 8 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/Ext/AITriggerType/Body.cpp create mode 100644 src/Ext/AITriggerType/Body.h create mode 100644 src/Ext/AITriggerType/Hooks.cpp diff --git a/CREDITS.md b/CREDITS.md index 0b5547141b..5d94c251c8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -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 diff --git a/Phobos.vcxproj b/Phobos.vcxproj index d45a6ac91f..ad001cec00 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -18,6 +18,8 @@ + + @@ -219,6 +221,7 @@ + diff --git a/YRpp b/YRpp index 6aa702acd4..5da505f7dd 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 6aa702acd40d9b166644e4e0b6a3b510130493fc +Subproject commit 5da505f7dd39fdbcd4090dd3eb583e723fa9a3eb diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 79bb79626b..c56ebee434 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -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 diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 096e7cd9c5..6d661cbfd9 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -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) diff --git a/src/Ext/AITriggerType/Body.cpp b/src/Ext/AITriggerType/Body.cpp new file mode 100644 index 0000000000..23b636ebb0 --- /dev/null +++ b/src/Ext/AITriggerType/Body.cpp @@ -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); +} diff --git a/src/Ext/AITriggerType/Body.h b/src/Ext/AITriggerType/Body.h new file mode 100644 index 0000000000..23eac1a0e1 --- /dev/null +++ b/src/Ext/AITriggerType/Body.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +// 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 + { + public: + // Nothing yet + + ExtData(AITriggerTypeClass* OwnerObject) : Extension(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 + { + 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); +}; diff --git a/src/Ext/AITriggerType/Hooks.cpp b/src/Ext/AITriggerType/Hooks.cpp new file mode 100644 index 0000000000..578989cb12 --- /dev/null +++ b/src/Ext/AITriggerType/Hooks.cpp @@ -0,0 +1,26 @@ +#include +#include + +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; + } +}