From 9adb0fe6177043bd5804434dd239586ced05176a Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 2 Dec 2022 06:07:58 +0100 Subject: [PATCH 01/34] Initial commit --- Phobos.vcxproj | 2 + src/Ext/House/Body.cpp | 267 +++++++ src/Ext/House/Body.h | 14 + src/Ext/HouseType/Body.cpp | 146 ++++ src/Ext/HouseType/Body.h | 60 ++ src/Ext/Rules/Body.cpp | 77 ++ src/Ext/Rules/Body.h | 24 + src/Ext/Team/Body.cpp | 1325 +++++++++++++++++++++++++++++++++++ src/Ext/Team/Body.h | 69 ++ src/Ext/TechnoType/Body.cpp | 102 +++ src/Ext/TechnoType/Body.h | 10 + src/Phobos.Ext.cpp | 2 + 12 files changed, 2098 insertions(+) create mode 100644 src/Ext/HouseType/Body.cpp create mode 100644 src/Ext/HouseType/Body.h diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 16350ba272..877367f389 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -29,6 +29,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 56b73155c3..817c160468 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -73,6 +73,249 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa } } +bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes) +{ + if (!pThis || !pItem) + return false; + + auto pHouseExt = HouseExt::ExtMap.Find(pThis); + if (!pHouseExt) + return false; + + auto pItemExt = TechnoTypeExt::ExtMap.Find(pItem); + if (!pItemExt) + return false; + + // Prerequisite.RequiredTheaters check + if (pItemExt->Prerequisite_RequiredTheaters.size() > 0) + { + int currentTheaterIndex = (int)ScenarioClass::Instance->Theater; + if (pItemExt->Prerequisite_RequiredTheaters.IndexOf(currentTheaterIndex) < 0) + return false; + } + + // TechLevel check + if (pThis->TechLevel < pItem->TechLevel) + return false; + + // BuildLimit checks + int nInstances = 0; + + for (auto pTechno : *TechnoClass::Array) + { + if (pTechno->Owner == pThis && pTechno->GetTechnoType() == pItem && pTechno->IsAlive && pTechno->Health > 0) + nInstances++; + } + + if (nInstances >= pItem->BuildLimit) + return false; + + bool prerequisiteNegativeMet = false; // Only one coincidence is needed + + // Ares Prerequisite.Negative list. + if (pItemExt->Prerequisite_Negative.size() > 0) + { + for (int idx : pItemExt->Prerequisite_Negative) + { + if (prerequisiteNegativeMet) + return false; + + if (idx < 0) // Is even possible this case? I have to check it + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + } + else + { + for (auto pObject : ownedBuildingTypes) + { + if (prerequisiteNegativeMet) + break; + + if (idx == pObject->ArrayIndex) + prerequisiteNegativeMet = true; + } + } + } + } + + //ValueableVector prerequisite = pItemExt->Prerequisite; + DynamicVectorClass prerequisiteOverride = pItem->PrerequisiteOverride; + + //UnitTypeClass* prerequisiteProcAlternate = RulesClass::Instance->PrerequisiteProcAlternate; + + bool prerequisiteMet = false; // All in this list must appear in ownedBuildingTypes + bool prerequisiteOverrideMet = false; // Only one coincidence is needed + + if (prerequisiteOverride.Count > 0) + { + for (int idx : prerequisiteOverride) + { + if (prerequisiteOverrideMet) + break; + + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + prerequisiteOverrideMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + } + else + { + for (auto pObject : ownedBuildingTypes) + { + if (prerequisiteOverrideMet) + break; + + if (idx == pObject->ArrayIndex) + prerequisiteOverrideMet = true; + } + } + } + } + + if (pItemExt->Prerequisite.size() > 0) + { + bool found = false; + //if (pItem) Debug::Log("[%s] prereq check????\n", pItem->ID); + for (int idx : pItemExt->Prerequisite) + { + found = false; + //Debug::Log("\tidx: %d:\n",idx); + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + } + else + { + //Debug::Log("\t[%s] (idx: %d):\n", TechnoTypeClass::Array->GetItem(idx)->ID, idx); + for (auto pObject : ownedBuildingTypes) + { + if (found) + break; + + if (idx == pObject->ArrayIndex) + found = true; + + //Debug::Log("\t\t[%s] (idx: %d) Found? %d\n", pObject->ID, pObject->ArrayIndex, found); + } + } + + if (!found) + break; + } + + prerequisiteMet = found; + } + else + { + prerequisiteMet = true; + } + + bool prerequisiteListsMet = false; + + // Ares Prerequisite lists + if (pItemExt->Prerequisite_Lists.Get() > 0) + { + bool found = false; + + for (auto list : pItemExt->Prerequisite_ListVector) + { + if (found) + break; + + for (int idx : list) + { + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + } + else + { + found = false; + + for (auto pObject : ownedBuildingTypes) + { + if (idx == pObject->ArrayIndex) + found = true; + + if (found) + break; + } + } + + if (!found) + break; + } + } + + prerequisiteListsMet = found; + } + + //Debug::Log("prerequisiteMet: %d, prerequisiteListsMet: %d, prerequisiteOverrideMet: %d\n", prerequisiteMet, prerequisiteListsMet, prerequisiteOverrideMet); + return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; +} + +bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClass ownedBuildingTypes) +{ + if (idx >= 0) + return false; + + DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); + + /*Debug::Log("DEBUG: SELECTED DEFAULT PREREQUISITES: %s [%d]\n", RulesExt::Global()->GenericPrerequisitesNames.GetItem(std::abs(idx)), idx); + for (int i : selectedPrerequisite) + { + auto aaa = TechnoTypeClass::Array->GetItem(i); + Debug::Log("[%s] -> %d\n", aaa->ID, i); + } + Debug::Log("\n"); + */ + if (selectedPrerequisite.Count == 0) + return false; + + bool found = false; + + for (auto idxItem : selectedPrerequisite) + { + if (found) + break; + + for (auto pObject : ownedBuildingTypes) + { + if (found) + break; + + if (idxItem == pObject->ArrayIndex) + found = true; + } + } + //Debug::Log("idx: %d -> found: %d\n", idx, found); + + return found; +} + +int HouseExt::FindGenericPrerequisite(const char* id) +{ + if (TechnoTypeClass::FindIndex(id) >= 0) + return 0; + + if (RulesExt::Global()->GenericPrerequisitesNames.Count == 0) + RulesExt::FillDefaultPrerequisites(); + + int i = 0; + for (auto str : RulesExt::Global()->GenericPrerequisitesNames) + { + if (_strcmpi(id, str) == 0) + return (-1 * i); + + ++i; + } + + return 0; +} + void HouseExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) { const char* pSection = this->OwnerObject()->PlainName; @@ -88,6 +331,25 @@ void HouseExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->RepairBaseNodes[i] = readBaseNodeRepairInfo[i < nWritten ? i : nWritten - 1]; } + Valueable readNewTeamsSelector_MergeUnclassifiedCategoryWith; + readNewTeamsSelector_MergeUnclassifiedCategoryWith.Read(exINI, pSection, "NewTeamsSelector.MergeUnclassifiedCategoryWith"); + this->NewTeamsSelector_MergeUnclassifiedCategoryWith = readNewTeamsSelector_MergeUnclassifiedCategoryWith.Get(); + + Valueable readNewTeamsSelector_UnclassifiedCategoryPercentage; + readNewTeamsSelector_UnclassifiedCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.UnclassifiedCategoryPercentage"); + this->NewTeamsSelector_UnclassifiedCategoryPercentage = readNewTeamsSelector_UnclassifiedCategoryPercentage.Get(); + + Valueable readNewTeamsSelector_GroundCategoryPercentage; + readNewTeamsSelector_GroundCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.GroundCategoryPercentage"); + this->NewTeamsSelector_GroundCategoryPercentage = readNewTeamsSelector_GroundCategoryPercentage.Get(); + + Valueable readNewTeamsSelector_NavalCategoryPercentage; + readNewTeamsSelector_NavalCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.NavalCategoryPercentage"); + this->NewTeamsSelector_NavalCategoryPercentage = readNewTeamsSelector_NavalCategoryPercentage.Get(); + + Valueable readNewTeamsSelector_AirCategoryPercentage; + readNewTeamsSelector_AirCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.AirCategoryPercentage"); + this->NewTeamsSelector_AirCategoryPercentage = readNewTeamsSelector_AirCategoryPercentage.Get(); } @@ -105,6 +367,11 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->Factory_NavyType) .Process(this->Factory_AircraftType) .Process(this->RepairBaseNodes) + .Process(this->NewTeamsSelector_MergeUnclassifiedCategoryWith) + .Process(this->NewTeamsSelector_UnclassifiedCategoryPercentage) + .Process(this->NewTeamsSelector_GroundCategoryPercentage) + .Process(this->NewTeamsSelector_NavalCategoryPercentage) + .Process(this->NewTeamsSelector_AirCategoryPercentage) ; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 92af3c0c23..0faaaed6cb 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -25,6 +25,12 @@ class HouseExt BuildingClass* Factory_NavyType; BuildingClass* Factory_AircraftType; + int NewTeamsSelector_MergeUnclassifiedCategoryWith; + double NewTeamsSelector_UnclassifiedCategoryPercentage; + double NewTeamsSelector_GroundCategoryPercentage; + double NewTeamsSelector_NavalCategoryPercentage; + double NewTeamsSelector_AirCategoryPercentage; + //Read from INI bool RepairBaseNodes[3]; @@ -36,6 +42,11 @@ class HouseExt , Factory_NavyType { nullptr } , Factory_AircraftType { nullptr } , RepairBaseNodes { false,false,false } + , NewTeamsSelector_MergeUnclassifiedCategoryWith { -1 } + , NewTeamsSelector_UnclassifiedCategoryPercentage { 0.25 } + , NewTeamsSelector_GroundCategoryPercentage { 0.25 } + , NewTeamsSelector_NavalCategoryPercentage { 0.25 } + , NewTeamsSelector_AirCategoryPercentage { 0.25 } { } virtual ~ExtData() = default; @@ -74,4 +85,7 @@ class HouseExt static int ActiveHarvesterCount(HouseClass* pThis); static int TotalHarvesterCount(HouseClass* pThis); static HouseClass* GetHouseKind(OwnerHouseKind kind, bool allowRandom, HouseClass* pDefault, HouseClass* pInvoker = nullptr, HouseClass* pVictim = nullptr); + static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes); + static bool HasGenericPrerequisite(int idx, const DynamicVectorClass ownedBuildingTypes); + static int FindGenericPrerequisite(const char* id); }; diff --git a/src/Ext/HouseType/Body.cpp b/src/Ext/HouseType/Body.cpp new file mode 100644 index 0000000000..abcb0c43b2 --- /dev/null +++ b/src/Ext/HouseType/Body.cpp @@ -0,0 +1,146 @@ +#include "Body.h" + +#include + +template<> const DWORD Extension::Canary = 0x1111111A; +HouseTypeExt::ExtContainer HouseTypeExt::ExtMap; + +void HouseTypeExt::ExtData::Initialize() +{ +} + +// ============================= +// load / save + +void HouseTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) +{ + auto pThis = this->OwnerObject(); + const char* pSection = pThis->ID; + + if (!pINI->GetSection(pSection)) + return; + + INI_EX exINI(pINI); + + this->NewTeamsSelector_MergeUnclassifiedCategoryWith.Read(exINI, pSection, "NewTeamsSelector.MergeUnclassifiedCategoryWith"); + this->NewTeamsSelector_UnclassifiedCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.UnclassifiedCategoryPercentage"); + this->NewTeamsSelector_GroundCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.GroundCategoryPercentage"); + this->NewTeamsSelector_AirCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.AirCategoryPercentage"); + this->NewTeamsSelector_NavalCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.NavalCategoryPercentage"); +} + +void HouseTypeExt::ExtData::CompleteInitialization() +{ + auto const pThis = this->OwnerObject(); + UNREFERENCED_PARAMETER(pThis); +} + +template +void HouseTypeExt::ExtData::Serialize(T& Stm) +{ + Stm + .Process(this->NewTeamsSelector_MergeUnclassifiedCategoryWith) + .Process(this->NewTeamsSelector_UnclassifiedCategoryPercentage) + .Process(this->NewTeamsSelector_GroundCategoryPercentage) + .Process(this->NewTeamsSelector_AirCategoryPercentage) + .Process(this->NewTeamsSelector_NavalCategoryPercentage) + ; +} + +void HouseTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) +{ + Extension::LoadFromStream(Stm); + this->Serialize(Stm); +} + +void HouseTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) +{ + Extension::SaveToStream(Stm); + this->Serialize(Stm); +} + +bool HouseTypeExt::ExtContainer::Load(HouseTypeClass* pThis, IStream* pStm) +{ + HouseTypeExt::ExtData* pData = this->LoadKey(pThis, pStm); + return pData != nullptr; +}; + +bool HouseTypeExt::LoadGlobals(PhobosStreamReader& Stm) +{ + return Stm.Success(); +} + +bool HouseTypeExt::SaveGlobals(PhobosStreamWriter& Stm) +{ + return Stm.Success(); +} +// ============================= +// container + +HouseTypeExt::ExtContainer::ExtContainer() : Container("HouseTypeClass") { } + +HouseTypeExt::ExtContainer::~ExtContainer() = default; + +// ============================= +// container hooks + +DEFINE_HOOK(0x511635, HouseTypeClass_CTOR_1, 0x5) +{ + GET(HouseTypeClass*, pItem, EAX); + + HouseTypeExt::ExtMap.FindOrAllocate(pItem); + + return 0; +} + +DEFINE_HOOK(0x511643, HouseTypeClass_CTOR_2, 0x5) +{ + GET(HouseTypeClass*, pItem, EAX); + + HouseTypeExt::ExtMap.FindOrAllocate(pItem); + + return 0; +} + +DEFINE_HOOK(0x5127CF, HouseTypeClass_DTOR, 0x6) +{ + GET(HouseTypeClass*, pItem, ESI); + + HouseTypeExt::ExtMap.Remove(pItem); + + return 0; +} + +DEFINE_HOOK_AGAIN(0x512480, HouseTypeClass_SaveLoad_Prefix, 0x5) +DEFINE_HOOK(0x512290, HouseTypeClass_SaveLoad_Prefix, 0x5) +{ + GET_STACK(HouseTypeClass*, pItem, 0x4); + GET_STACK(IStream*, pStm, 0x8); + + HouseTypeExt::ExtMap.PrepareStream(pItem, pStm); + + return 0; +} + +DEFINE_HOOK(0x51246D, HouseTypeClass_Load_Suffix, 0x5) +{ + HouseTypeExt::ExtMap.LoadStatic(); + return 0; +} + +DEFINE_HOOK(0x51255C, HouseTypeClass_Save_Suffix, 0x5) +{ + HouseTypeExt::ExtMap.SaveStatic(); + return 0; +} + +DEFINE_HOOK_AGAIN(0x51215A, HouseTypeClass_LoadFromINI, 0x5) +DEFINE_HOOK(0x51214F, HouseTypeClass_LoadFromINI, 0x5) +{ + GET(HouseTypeClass*, pItem, EBX); + GET_BASE(CCINIClass*, pINI, 0x8); + + HouseTypeExt::ExtMap.LoadFromINI(pItem, pINI); + + return 0; +} diff --git a/src/Ext/HouseType/Body.h b/src/Ext/HouseType/Body.h new file mode 100644 index 0000000000..701f9bc5c3 --- /dev/null +++ b/src/Ext/HouseType/Body.h @@ -0,0 +1,60 @@ +#pragma once +#include + +#include +#include +#include + +class HouseTypeExt +{ +public: + using base_type = HouseTypeClass; + + class ExtData final : public Extension + { + public: + Nullable NewTeamsSelector_MergeUnclassifiedCategoryWith; + Nullable NewTeamsSelector_UnclassifiedCategoryPercentage; + Nullable NewTeamsSelector_GroundCategoryPercentage; + Nullable NewTeamsSelector_NavalCategoryPercentage; + Nullable NewTeamsSelector_AirCategoryPercentage; + + ExtData(HouseTypeClass* OwnerObject) : Extension(OwnerObject) + , NewTeamsSelector_MergeUnclassifiedCategoryWith { } + , NewTeamsSelector_UnclassifiedCategoryPercentage { } + , NewTeamsSelector_GroundCategoryPercentage { } + , NewTeamsSelector_NavalCategoryPercentage { } + , NewTeamsSelector_AirCategoryPercentage { } + { } + + virtual ~ExtData() = default; + + virtual void LoadFromINIFile(CCINIClass* pINI) override; + virtual void Initialize() override; + virtual void CompleteInitialization(); + + virtual void InvalidatePointer(void* ptr, bool bRemoved) override + { + } + + virtual void LoadFromStream(PhobosStreamReader& Stm) override; + virtual void SaveToStream(PhobosStreamWriter& Stm) override; + + private: + template + void Serialize(T& Stm); + }; + + class ExtContainer final : public Container + { + public: + ExtContainer(); + ~ExtContainer(); + + virtual bool Load(HouseTypeClass* pThis, IStream* pStm) override; + }; + + static ExtContainer ExtMap; + static bool LoadGlobals(PhobosStreamReader& Stm); + static bool SaveGlobals(PhobosStreamWriter& Stm); +}; diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index c32d37eab4..abf2e55b9c 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -144,6 +144,18 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) AIScriptsLists.AddItem(objectsList); objectsList.Clear(); } + + this->NewTeamsSelector.Read(exINI, "AI", "NewTeamsSelector"); + this->NewTeamsSelector_SplitTriggersByCategory.Read(exINI, "AI", "NewTeamsSelector.SplitTriggersByCategory"); + this->NewTeamsSelector_EnableFallback.Read(exINI, "AI", "NewTeamsSelector.EnableFallback"); + this->NewTeamsSelector_MergeUnclassifiedCategoryWith.Read(exINI, "AI", "NewTeamsSelector.MergeUnclassifiedCategoryWith"); + this->NewTeamsSelector_UnclassifiedCategoryPercentage.Read(exINI, "AI", "NewTeamsSelector.UnclassifiedCategoryPercentage"); + this->NewTeamsSelector_GroundCategoryPercentage.Read(exINI, "AI", "NewTeamsSelector.GroundCategoryPercentage"); + this->NewTeamsSelector_AirCategoryPercentage.Read(exINI, "AI", "NewTeamsSelector.AirCategoryPercentage"); + this->NewTeamsSelector_NavalCategoryPercentage.Read(exINI, "AI", "NewTeamsSelector.NavalCategoryPercentage"); + + // Section Generic Prerequisites + FillDefaultPrerequisites(); } // this runs between the before and after type data loading methods for rules ini @@ -182,6 +194,61 @@ bool RulesExt::DetailsCurrentlyEnabled(int const minDetailLevel) && DetailsCurrentlyEnabled(); } +void RulesExt::FillDefaultPrerequisites() +{ + if (RulesExt::Global()->GenericPrerequisitesNames.Count != 0) + return; + //RulesExt::Global()->GenericPrerequisitesNames.Clear(); + + CCINIClass::INI_Rules; + DynamicVectorClass empty; + RulesExt::Global()->GenericPrerequisitesNames.AddItem("POWER"); // -1 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisitePower); + RulesExt::Global()->GenericPrerequisitesNames.AddItem("FACTORY"); // -2 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteFactory); + RulesExt::Global()->GenericPrerequisitesNames.AddItem("BARRACKS"); // -3 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteBarracks); + RulesExt::Global()->GenericPrerequisitesNames.AddItem("RADAR"); // -4 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteRadar); + RulesExt::Global()->GenericPrerequisitesNames.AddItem("TECH"); // -5 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteTech); + RulesExt::Global()->GenericPrerequisitesNames.AddItem("PROC"); // -6 + RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteProc); + + // If [GenericPrerequisites] is present will be added after these. + // Also the originals can be replaced by new ones + int genericPreqsCount = CCINIClass::INI_Rules->GetKeyCount("GenericPrerequisites"); + for (int i = 0; i < genericPreqsCount; ++i) + { + DynamicVectorClass objectsList; + char* context = nullptr; + CCINIClass::INI_Rules->ReadString("GenericPrerequisites", CCINIClass::INI_Rules->GetKeyName("GenericPrerequisites", i), "", Phobos::readBuffer); + char* name = (char*)CCINIClass::INI_Rules->GetKeyName("GenericPrerequisites", i); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + int idx = BuildingTypeClass::FindIndex(cur); + if (idx >= 0) + objectsList.AddItem(idx); + } + + int index = RulesExt::Global()->GenericPrerequisitesNames.FindItemIndex(name); + if (index < 7 && index > 0) + { + // Overwrites a vanilla generic prerequisite + RulesExt::Global()->GenericPrerequisites[index] = objectsList; + } + else + { + // New + RulesExt::Global()->GenericPrerequisitesNames.AddItem(name); + RulesExt::Global()->GenericPrerequisites.AddItem(objectsList); + } + + objectsList.Clear(); + } +} + // ============================= // load / save @@ -230,6 +297,16 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->ToolTip_Background_Opacity) .Process(this->ToolTip_Background_BlurSize) .Process(this->RadialIndicatorVisibility) + .Process(this->GenericPrerequisites) + .Process(this->GenericPrerequisitesNames) + .Process(this->NewTeamsSelector) + .Process(this->NewTeamsSelector_SplitTriggersByCategory) + .Process(this->NewTeamsSelector_EnableFallback) + .Process(this->NewTeamsSelector_MergeUnclassifiedCategoryWith) + .Process(this->NewTeamsSelector_UnclassifiedCategoryPercentage) + .Process(this->NewTeamsSelector_GroundCategoryPercentage) + .Process(this->NewTeamsSelector_AirCategoryPercentage) + .Process(this->NewTeamsSelector_NavalCategoryPercentage) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 763e7e5624..ba36be7ffd 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -69,6 +69,19 @@ class RulesExt Valueable RadialIndicatorVisibility; + DynamicVectorClass> GenericPrerequisites; + DynamicVectorClass GenericPrerequisitesNames; + + Valueable NewTeamsSelector; + Valueable NewTeamsSelector_SplitTriggersByCategory; + Valueable NewTeamsSelector_EnableFallback; + Valueable NewTeamsSelector_MergeUnclassifiedCategoryWith; + Valueable NewTeamsSelector_UnclassifiedCategoryPercentage; + Valueable NewTeamsSelector_GroundCategoryPercentage; + Valueable NewTeamsSelector_NavalCategoryPercentage; + Valueable NewTeamsSelector_AirCategoryPercentage; + + ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , InfantryGainSelfHealCap {} @@ -106,6 +119,16 @@ class RulesExt , ToolTip_Background_Opacity { 100 } , ToolTip_Background_BlurSize { 0.0f } , RadialIndicatorVisibility { AffectedHouse::Allies } + , GenericPrerequisites { } + , GenericPrerequisitesNames { } + , NewTeamsSelector { false } + , NewTeamsSelector_SplitTriggersByCategory { true } + , NewTeamsSelector_EnableFallback { false } + , NewTeamsSelector_MergeUnclassifiedCategoryWith { -1 } + , NewTeamsSelector_UnclassifiedCategoryPercentage { 0.25 } + , NewTeamsSelector_GroundCategoryPercentage { 0.25 } + , NewTeamsSelector_NavalCategoryPercentage { 0.25 } + , NewTeamsSelector_AirCategoryPercentage { 0.25 } { } virtual ~ExtData() = default; @@ -159,4 +182,5 @@ class RulesExt static bool DetailsCurrentlyEnabled(); static bool DetailsCurrentlyEnabled(int minDetailLevel); + static void FillDefaultPrerequisites(); }; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 878d9c7995..4e514c6679 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -89,3 +89,1328 @@ DEFINE_HOOK(0x6EC55A, TeamClass_Save_Suffix, 0x5) TeamExt::ExtMap.SaveStatic(); return 0; } + +DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) +{ + enum { UseOriginalSelector = 0x4F8A63, SkipCode = 0x4F8B08 }; + + GET(HouseClass*, pHouse, ESI); + + bool houseIsHuman = pHouse->IsHumanPlayer; + if (SessionClass::IsCampaign()) + houseIsHuman = pHouse->IsHumanPlayer || pHouse->IsInPlayerControl; + + if (houseIsHuman || pHouse->Type->MultiplayPassive) + return SkipCode; + + auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + if (!pHouseExt) + return SkipCode; + + auto pHouseTypeExt = HouseTypeExt::ExtMap.Find(pHouse->Type); + if (!pHouseTypeExt) + return SkipCode; + + if (!RulesExt::Global()->NewTeamsSelector) + return UseOriginalSelector; + + // Reset Team selection countdown + int countdown = RulesClass::Instance->TeamDelays[(int)pHouse->AIDifficulty]; + pHouse->TeamDelayTimer.Start(countdown); + + int totalActiveTeams = 0; + int activeTeams = 0; + + int totalGroundCategoryTriggers = 0; + int totalUnclassifiedCategoryTriggers = 0; + int totalNavalCategoryTriggers = 0; + int totalAirCategoryTriggers = 0; + + DynamicVectorClass validTriggerCandidates; + DynamicVectorClass validTriggerCandidatesGroundOnly; + DynamicVectorClass validTriggerCandidatesNavalOnly; + DynamicVectorClass validTriggerCandidatesAirOnly; + DynamicVectorClass validTriggerCandidatesUnclassifiedOnly; + int dice = ScenarioClass::Instance->Random.RandomRanged(1, 100); + + //if (pHouse->IsHumanPlayer || pHouse->IsInPlayerControl) + //return ContinueFlow; + + //This house must have enabled the triggers + if (dice <= pHouse->RatioAITriggerTeam && pHouse->AITriggersActive) + { + bool splitTriggersByCategory = RulesExt::Global()->NewTeamsSelector_SplitTriggersByCategory; + bool isFallbackEnabled = RulesExt::Global()->NewTeamsSelector_EnableFallback; + teamCategory validCategory = teamCategory::None; + + double percentageUnclassifiedTriggers = 0.0; + double percentageGroundTriggers = 0.0; + double percentageNavalTriggers = 0.0; + double percentageAirTriggers = 0.0; + + if (splitTriggersByCategory) + { + int mergeUnclassifiedCategoryWith = pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.isset() ? pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.Get() : RulesExt::Global()->NewTeamsSelector_MergeUnclassifiedCategoryWith; + percentageUnclassifiedTriggers = pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_UnclassifiedCategoryPercentage; // Mixed teams + percentageGroundTriggers = pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_GroundCategoryPercentage; // Only ground + percentageNavalTriggers = pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_NavalCategoryPercentage; // Only Naval=yes + percentageAirTriggers = pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_AirCategoryPercentage; // Only Aircrafts & jumpjets + + // Merge mixed category with another category, if set + if (mergeUnclassifiedCategoryWith >= 0) + { + switch (mergeUnclassifiedCategoryWith) + { + case (int)teamCategory::Ground: + percentageGroundTriggers += percentageUnclassifiedTriggers; + break; + + case (int)teamCategory::Air: + percentageAirTriggers += percentageUnclassifiedTriggers; + break; + + case (int)teamCategory::Naval: + percentageNavalTriggers += percentageUnclassifiedTriggers; + break; + + default: + break; + } + + percentageUnclassifiedTriggers = 0.0; + } + + // TO-DO: HERE load new values from Rulesmd.ini, if set + + percentageUnclassifiedTriggers = percentageUnclassifiedTriggers < 0.0 || percentageUnclassifiedTriggers > 1.0 ? 0.0 : percentageUnclassifiedTriggers; + percentageGroundTriggers = percentageGroundTriggers < 0.0 || percentageGroundTriggers > 1.0 ? 0.0 : percentageGroundTriggers; + percentageNavalTriggers = percentageNavalTriggers < 0.0 || percentageNavalTriggers > 1.0 ? 0.0 : percentageNavalTriggers; + percentageAirTriggers = percentageAirTriggers < 0.0 || percentageAirTriggers > 1.0 ? 0.0 : percentageAirTriggers; + + double totalPercengates = percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers; + if (totalPercengates > 1.0 || totalPercengates <= 0.0) + splitTriggersByCategory = false; + // Note: if the sum of all percentages is less than 100% then that empty space will work like "no categories" + + if (splitTriggersByCategory) + { + int categoryDice = ScenarioClass::Instance->Random.RandomRanged(1, 100); + + if (categoryDice == 0) + { + splitTriggersByCategory = false; + } + else if (categoryDice <= (int)(percentageUnclassifiedTriggers * 100.0)) + { + validCategory = teamCategory::Unclassified; + } + else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers) * 100.0)) + { + validCategory = teamCategory::Ground; + } + else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers) * 100.0)) + { + validCategory = teamCategory::Naval; + } + else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers) * 100.0)) + { + validCategory = teamCategory::Air; + } + else + { + splitTriggersByCategory = false; + } + } + } + + // PREPARING... + + int houseIdx = pHouse->ArrayIndex; + int sideIdx = pHouse->SideIndex + 1; + //int enemyHouseIndex = pHouse->EnemyHouseIndex >= 0 ? pHouse->EnemyHouseIndex : -1; + auto houseDifficulty = pHouse->AIDifficulty; + //int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); + int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); + int activeDefenseTeamsCount = 0; + int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); + double totalWeight = 0.0; + double totalWeightGroundOnly = 0.0; + double totalWeightNavalOnly = 0.0; + double totalWeightAirOnly = 0.0; + double totalWeightUnclassifiedOnly = 0.0; + + // Check if the running teams by the house already reached all the limits + DynamicVectorClass activeTeamsList; + + for (auto const pRunningTeam : *TeamClass::Array) + { + totalActiveTeams++; + int teamHouseIdx = pRunningTeam->Owner->ArrayIndex; + //auto teamHouse = pRunningTeam->GetOwningHouse(); + if (teamHouseIdx != houseIdx) + continue; + + activeTeamsList.AddItem(pRunningTeam); + + if (pRunningTeam->Type->IsBaseDefense && activeDefenseTeamsCount < maxBaseDefenseTeams) + activeDefenseTeamsCount++; + } + + activeTeams = activeTeamsList.Count; + // We will use these for discarding triggers + bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; + bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; + + if (hasReachedMaxDefensiveTeamsLimit) + Debug::Log("DEBUG: House [%s] (Idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + + if (hasReachedMaxTeamsLimit) + { + Debug::Log("DEBUG: House [%s] (Idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + // Build a list of built structures by the house + DynamicVectorClass ownedBuildingTypes; + for (auto building : pHouse->Buildings) + { + ownedBuildingTypes.AddUnique(building->Type); + } + + struct recruitableUnit + { + TechnoTypeClass* object = nullptr; + int count = 1; + + bool operator==(const TechnoTypeClass* other) const + { + return (object == other); + } + + bool operator==(const recruitableUnit other) const + { + return (object == other.object); + } + }; + + // Build a list of recruitable units by the house + DynamicVectorClass recruitableUnits; + + for (auto pTechno : *TechnoClass::Array) + { + if (pTechno->WhatAmI() == AbstractType::Building) + continue; + + FootClass* pFoot = static_cast(pTechno); + + if (!pFoot + || !pTechno->IsAlive + || pTechno->Health <= 0 + || !pTechno->IsOnMap // Note: underground movement is considered "IsOnMap == false" + || pTechno->Transporter + || pTechno->Absorbed + || !pFoot->CanBeRecruited(pHouse)) + { + continue; + } + + auto pTechnoType = pTechno->GetTechnoType(); + bool found = false; + + for (int i = 0; i < recruitableUnits.Count; i++) + { + if (recruitableUnits[i].object == pTechnoType) + { + recruitableUnits[i].count++; + found = true; + break; + } + } + + if (!found) + { + recruitableUnit newRecruitable; + newRecruitable.object = pTechnoType; + recruitableUnits.AddItem(newRecruitable); + } + } + + //Debug::Log("List of recruitable units of this house:\n"); + //for (auto item : recruitableUnits) + //{ + //if (item.object) + //Debug::Log("[%s] = %d\n", item.object->ID, item.count); + //} + //Debug::Log("AAA\n"); + + HouseClass* targetHouse = nullptr; + if (pHouse->EnemyHouseIndex >= 0) + targetHouse = HouseClass::Array->GetItem(pHouse->EnemyHouseIndex); + + bool onlyCheckImportantTriggers = false; + + // Gather all the trigger candidates into one place for posterior fast calculations + for (auto const pTrigger : *AITriggerTypeClass::Array) + { + if (!pTrigger) + continue; + + int triggerHouse = pTrigger->HouseIndex; + int triggerSide = pTrigger->SideIndex; + + // Ignore the deactivated triggers + if (pTrigger->IsEnabled) + { + // The trigger must be compatible with the owner + if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + { + // "ConditionType=-1" will be skipped, is always valid (and save CPU) + if ((int)pTrigger->ConditionType >= 0) + { + if ((int)pTrigger->ConditionType == 0) + { + // Simulate case 0: "enemy owns" + if (!pTrigger->ConditionObject) + continue; + + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 1) + { + // Simulate case 0: "house owns" + if (!pTrigger->ConditionObject) + continue; + + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 7) + { + // Simulate case 7: "civilian owns" + if (!pTrigger->ConditionObject) + continue; + + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 8) + { + // Simulate case 0: "enemy owns" but without restrict it against only 1 enemy house + if (!pTrigger->ConditionObject) + continue; + + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 9) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 10) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 11) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 12) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 13) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 14) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 14: Like in case 9 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 15) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 15: Like in case 10 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 16) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 16: Like in case 11 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 17) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + { + // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); + + if (!isConditionMet) + continue; + } + } + else + { + // Other cases from vanilla game + //bool cm = pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit); + //Debug::Log("[%s], \"%s\"\nOld ConditionMet section (%d, %s): %d [-, %d, %d]\n\n", pTrigger->ID, pTrigger->Team1->Name, (int)pTrigger->ConditionType, (pTrigger->ConditionObject ? pTrigger->ConditionObject->ID : ""), cm, pTrigger->Conditions->ComparatorOperand, pTrigger->Conditions->ComparatorType); + + if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) + continue; + } + } + /*else + { + bool cm = false; + }*/ + + if (onlyCheckImportantTriggers) + { + if (pTrigger->Weight_Current < 5000) + continue; + } + + auto pTriggerTeam1Type = pTrigger->Team1; + if (!pTriggerTeam1Type) + continue; + + // No more defensive teams needed + if (pTriggerTeam1Type->IsBaseDefense && hasReachedMaxDefensiveTeamsLimit) + continue; + + // If this type of Team reached the max then skip it + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam1Type) + count++; + } + + if (count >= pTriggerTeam1Type->Max) + continue; + + teamCategory teamIsCategory = teamCategory::None; + + // Analyze what kind of category is this main team if the feature is enabled + if (splitTriggersByCategory) + { + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + { + // If they team has mixed members there is no need to continue + if (teamIsCategory == teamCategory::Unclassified) + break; + + if (entry.Amount > 0) + { + /* + What category goes each unit? there is the table: + + - Air category magic words: + ConsideredAircraft=yes + JumpJet=yes + BalloonHover=yes + + - Maval category magic words: + Naval=yes + MovementRestrictedTo=Water + + - Mixed category + MovementZone=Amphibious + + + - Ground category magic words: + Anyone that doesn't meet the previous magic words + */ + + if (entry.Type) + { + if (entry.Type->WhatAmI() == AbstractType::AircraftType || entry.Type->ConsideredAircraft) + { + // For now the team is from air category + teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Air ? teamCategory::Air : teamCategory::Unclassified; + } + else if (entry.Type->Naval && (entry.Type->MovementZone != MovementZone::Amphibious && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer || entry.Type->MovementZone != MovementZone::AmphibiousCrusher)) + { + // For now the team is from naval category + teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Naval ? teamCategory::Naval : teamCategory::Unclassified; + } + else if (teamIsCategory != teamCategory::Naval && teamIsCategory != teamCategory::Air) + { + // For now the team doesn't belong to the previous categories + teamIsCategory = teamIsCategory != teamCategory::Unclassified ? teamCategory::Ground : teamCategory::Unclassified; + } + } + } + else + { + break; + } + } + } + + bool allObjectsCanBeBuiltOrRecruited = true; + //Debug::Log("[%s] [%s] is evaluating unit prerequisites...\n", pTrigger->ID, pTriggerTeam1Type->ID); + + if (pTriggerTeam1Type->Autocreate) + { + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + { + // Check if each unit in the taskforce meets the structure prerequisites + if (entry.Amount > 0) + { + if (!entry.Type) + continue; + + TechnoTypeClass* object = entry.Type; + //bool canBeBuilt = pHouse->AllPrerequisitesAvailable(object, ownedBuildingTypes, ownedBuildingTypes.Count); // Old + + // Experimental + bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildingTypes); + + if (!canBeBuilt) + { + //Debug::Log("[%s] not ok...\n", object->ID); + allObjectsCanBeBuiltOrRecruited = false; + break; + } + else + { + //Debug::Log("[%s] build prerequisites met!\n", object->ID); + } + } + else + { + break; + } + } + } + else + { + allObjectsCanBeBuiltOrRecruited = false; + } + + //if (allObjectsCanBeBuiltOrRecruited) + //Debug::Log("[%s] [%s] can build units!...\n", pTrigger->ID, pTriggerTeam1Type->ID); + + if (!allObjectsCanBeBuiltOrRecruited && pTriggerTeam1Type->Recruiter) + { + allObjectsCanBeBuiltOrRecruited = true; + //Debug::Log("[%s] [%s] checking if it can recruit...\n", pTrigger->ID, pTriggerTeam1Type->ID); + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + { + // Check if each unit in the taskforce has the available recruitable units in the map + if (allObjectsCanBeBuiltOrRecruited && entry.Amount > 0) + { + bool canBeRecruited = false; + //Debug::Log("[%s] checking if there are recruitable...\n", entry.Type->ID); + + for (auto item : recruitableUnits) + { + if (item.object == entry.Type) + { + if (item.count >= entry.Amount) + canBeRecruited = true; + + break; + } + } + + if (!canBeRecruited) + { + //Debug::Log("[%s] %d can not be recruited!\n", entry.Type->ID, entry.Amount); + allObjectsCanBeBuiltOrRecruited = false; + break; + } + else + { + //Debug::Log("[%s] can recruit %d units!\n", entry.Type->ID, entry.Amount); + } + } + } + } + + // We can't let AI cheat in this trigger because doesn't have the required tech tree available + if (!allObjectsCanBeBuiltOrRecruited) + continue; + + // Special case: triggers become very important if they reach the max priority (value 5000) + // They get stored in a different list and all previous triggers are discarded. + if (pTrigger->Weight_Current >= 5000 && !onlyCheckImportantTriggers) + { + // First time only + if (validTriggerCandidates.Count > 0) + { + validTriggerCandidates.Clear(); + validTriggerCandidatesGroundOnly.Clear(); + validTriggerCandidatesNavalOnly.Clear(); + validTriggerCandidatesAirOnly.Clear(); + validTriggerCandidatesUnclassifiedOnly.Clear(); + + validCategory = teamCategory::None; + } + + // Reset and now only add important triggers to the list + onlyCheckImportantTriggers = true; + totalWeight = 0.0; + splitTriggersByCategory = false; // VIP teams breaks the categories logic (on purpose) + } + + // Passed all checks, save this trigger for later. + // The idea behind this is to simulate an ordered list of weights and once we throw the dice we'll know the winner trigger: The more weight means more possibilities to be selected. + totalWeight += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + TriggerElementWeight item; + item.Trigger = pTrigger; + item.Weight = totalWeight; + item.Category = teamIsCategory; + + validTriggerCandidates.AddItem(item); + + if (splitTriggersByCategory) + { + TriggerElementWeight itemGroundOnly; + TriggerElementWeight itemAirOnly; + TriggerElementWeight itemNavalOnly; + TriggerElementWeight itemUnclassifiedOnly; + + switch (teamIsCategory) + { + case teamCategory::Ground: + totalWeightGroundOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemGroundOnly.Trigger = pTrigger; + itemGroundOnly.Weight = totalWeightGroundOnly; + itemGroundOnly.Category = teamIsCategory; + + validTriggerCandidatesGroundOnly.AddItem(itemGroundOnly); + totalGroundCategoryTriggers++; + break; + + case teamCategory::Air: + totalWeightAirOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemAirOnly.Trigger = pTrigger; + itemAirOnly.Weight = totalWeightAirOnly; + itemAirOnly.Category = teamIsCategory; + + validTriggerCandidatesAirOnly.AddItem(itemAirOnly); + totalAirCategoryTriggers++; + break; + + case teamCategory::Naval: + totalWeightNavalOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemNavalOnly.Trigger = pTrigger; + itemNavalOnly.Weight = totalWeightNavalOnly; + itemNavalOnly.Category = teamIsCategory; + + validTriggerCandidatesNavalOnly.AddItem(itemNavalOnly); + totalNavalCategoryTriggers++; + break; + + case teamCategory::Unclassified: + totalWeightUnclassifiedOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemUnclassifiedOnly.Trigger = pTrigger; + itemUnclassifiedOnly.Weight = totalWeightUnclassifiedOnly; + itemUnclassifiedOnly.Category = teamIsCategory; + + validTriggerCandidatesUnclassifiedOnly.AddItem(itemUnclassifiedOnly); + totalUnclassifiedCategoryTriggers++; + break; + + default: + break; + } + } + } + } + } + + if (splitTriggersByCategory) + { + Debug::Log("DEBUG: Category percentages:\nMixed teams: %f\nGround teams: %f\nNaval teams: %f\nAir teams: %f\n", percentageUnclassifiedTriggers, percentageGroundTriggers, percentageNavalTriggers, percentageAirTriggers); + + switch (validCategory) + { + case teamCategory::Ground: + Debug::Log("DEBUG: This time only will be picked GROUND teams.\n"); + break; + + case teamCategory::Unclassified: + Debug::Log("DEBUG: This time only will be picked MIXED teams.\n"); + break; + + case teamCategory::Naval: + Debug::Log("DEBUG: This time only will be picked NAVAL teams.\n"); + break; + + case teamCategory::Air: + Debug::Log("DEBUG: This time only will be picked AIR teams.\n"); + break; + + default: + Debug::Log("DEBUG: This time teams categories are DISABLED.\n"); + break; + } + } + + if (validTriggerCandidates.Count == 0) + { + Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers for now. A new attempt will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + if ((validCategory == teamCategory::Ground && totalGroundCategoryTriggers == 0) || + (validCategory == teamCategory::Unclassified && totalUnclassifiedCategoryTriggers == 0) || + (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) || + (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) + { + Debug::Log("DEBUG: [%s] (Idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + + if (!isFallbackEnabled) + return SkipCode; + + Debug::Log("... BUT fallback mode is enabled so now it will check all triggers available.\n"); + validCategory = teamCategory::None; + } + + AITriggerTypeClass* selectedTrigger = nullptr; + double weightDice = 0.0; + double lastWeight = 0.0; + bool found = false; + + switch (validCategory) + { + case teamCategory::None: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; + // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeight, weightDice); + // Debug::Log("Picking the trigger with highest weight from the list (No category set):\n"); + + for (auto element : validTriggerCandidates) + { + // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + // Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + // Debug::Log(" ... \n"); + } + } + break; + + case teamCategory::Ground: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; + // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightGroundOnly, weightDice); + // Debug::Log("Picking the trigger with highest weight from the list (Ground category):\n"); + + for (auto element : validTriggerCandidatesGroundOnly) + { + // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + // Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + // Debug::Log(" ... \n"); + } + } + break; + + case teamCategory::Unclassified: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; + // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightUnclassifiedOnly, weightDice); + // Debug::Log("Picking the trigger with highest weight from the list (Mixed category):\n"); + + for (auto element : validTriggerCandidatesUnclassifiedOnly) + { + // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + // Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + // Debug::Log(" ... \n"); + } + } + break; + + case teamCategory::Naval: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; + // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightNavalOnly, weightDice); + // Debug::Log("Picking the trigger with highest weight from the list (Naval category):\n"); + + for (auto element : validTriggerCandidatesNavalOnly) + { + // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + // Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + // Debug::Log(" ... \n"); + } + } + break; + + case teamCategory::Air: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; + // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightAirOnly, weightDice); + // Debug::Log("Picking the trigger with highest weight from the list (Air category):\n"); + + for (auto element : validTriggerCandidatesAirOnly) + { + // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + // Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + // Debug::Log(" ... \n"); + } + } + break; + + default: + break; + } + /* + Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); + lastWeight = element.Weight; + + if (weightDice < element.WeightOnlyNaval && !found) + { + Debug::Log(" ... CANDIDATE!\n"); + selectedTrigger = element.Trigger; + found = true; + } + else + { + Debug::Log(" ... \n"); + } + */ + + if (!selectedTrigger) + { + Debug::Log("DEBUG: House [%s] (Idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + if (selectedTrigger->Weight_Current >= 5000.0 + && selectedTrigger->Weight_Minimum <= 4999.0) + { + // Next time this trigger will be out of the important triggers list + selectedTrigger->Weight_Current = 4999.0; + } + + // We have a winner + Debug::Log("DEBUG: House [%s] (Idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); + + //for (auto entry : selectedTrigger->Team1->TaskForce->Entries) + //{ + //if (entry.Amount > 0) + //Debug::Log("[%s] = %d\n", entry.Type->ID, entry.Amount); + //else + //break; + //} + //Debug::Log("\n"); + + auto pTriggerTeam1Type = selectedTrigger->Team1; + if (pTriggerTeam1Type) + { + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam1Type) + count++; + } + + if (count < pTriggerTeam1Type->Max) + { + if (auto newTeam = pTriggerTeam1Type->CreateTeam(pHouse)) + newTeam->NeedsToDisappear = false; + } + } + + auto pTriggerTeam2Type = selectedTrigger->Team2; + if (pTriggerTeam2Type) + { + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam2Type) + count++; + } + + if (count < pTriggerTeam2Type->Max) + { + if (auto newTeam = pTriggerTeam2Type->CreateTeam(pHouse)) + newTeam->NeedsToDisappear = false; + } + } + + //Debug::Log("TeamClass::Array->Count: %d\n", TeamClass::Array->Count); + } + + + //selectedTeams46 = newTeamsList; + return SkipCode; +} + +bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list) +{ + bool result = false; + int counter = 0; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && ((!allies && pObject->Owner == pHouse) || (allies && pHouse != pObject->Owner && pHouse->IsAlliedWith(pObject->Owner))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list) +{ + bool result = false; + int counter = 0; + + if (pEnemy && pHouse->IsAlliedWith(pEnemy) && !onlySelectedEnemy) + pEnemy = nullptr; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner != pHouse + && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) +{ + bool result = false; + int counter = 0; + + for (auto pHouse : *HouseClass::Array) + { + if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) + continue; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner == pHouse + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list) +{ + bool result = true; + + if (list.Count == 0) + return false; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!result) + break; + + int counter = 0; + result = true; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject && + pObject->IsAlive && + pObject->Health > 0 && + pObject->Owner == pHouse && + pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + return result; +} + +bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list) +{ + bool result = true; + + if (pEnemy && pHouse->IsAlliedWith(pEnemy)) + pEnemy = nullptr; + + if (list.Count == 0) + return false; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!result) + break; + + int counter = 0; + result = true; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner != pHouse + && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + return result; +} + +bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list) +{ + bool result = true; + + if (list.Count == 0) + return false; + + // Any neutral house should be capable to meet the prerequisites + for (auto pHouse : *HouseClass::Array) + { + if (!result) + break; + + bool foundAll = true; + + if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) + continue; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!foundAll) + break; + + int counter = 0; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject && + pObject->IsAlive && + pObject->Health > 0 && + pObject->Owner == pHouse && + pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + foundAll = counter < pThis->Conditions->ComparatorType; + break; + case 1: + foundAll = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + foundAll = counter == pThis->Conditions->ComparatorType; + break; + case 3: + foundAll = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + foundAll = counter > pThis->Conditions->ComparatorType; + break; + case 5: + foundAll = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + if (!foundAll) + result = false; + } + + return result; +} diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index 01033f3ba6..559e39b185 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -1,12 +1,74 @@ #pragma once #include +#include #include #include #include #include +#include +#include +#include #include +enum teamCategory +{ + None = 0, // No category. Should be default value + Ground = 1, + Air = 2, + Naval = 3, + Unclassified = 4 +}; + +struct TriggerElementWeight +{ + double Weight = 0.0; + AITriggerTypeClass* Trigger = nullptr; + teamCategory Category = teamCategory::None; + + //need to define a == operator so it can be used in array classes + bool operator==(const TriggerElementWeight& other) const + { + return (Trigger == other.Trigger && Weight == other.Weight && Category == other.Category); + } + + //unequality + bool operator!=(const TriggerElementWeight& other) const + { + return (Trigger != other.Trigger || Weight != other.Weight || Category == other.Category); + } + + bool operator<(const TriggerElementWeight& other) const + { + return (Weight < other.Weight); + } + + bool operator<(const double other) const + { + return (Weight < other); + } + + bool operator>(const TriggerElementWeight& other) const + { + return (Weight > other.Weight); + } + + bool operator>(const double other) const + { + return (Weight > other); + } + + bool operator==(const double other) const + { + return (Weight == other); + } + + bool operator!=(const double other) const + { + return (Weight != other); + } +}; + class TeamExt { public: @@ -81,4 +143,11 @@ class TeamExt static ExtContainer ExtMap; + static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list); + static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list); + static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list); + static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list); + static bool NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list); + static bool NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list); + }; diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 1f843181d3..749f40db53 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -271,6 +272,101 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ProneSecondaryFireFLH.Read(exArtINI, pArtSection, "ProneSecondaryFireFLH"); this->DeployedPrimaryFireFLH.Read(exArtINI, pArtSection, "DeployedPrimaryFireFLH"); this->DeployedSecondaryFireFLH.Read(exArtINI, pArtSection, "DeployedSecondaryFireFLH"); + + // Prerequisite.RequiredTheaters contains a list of theader names + this->Prerequisite_RequiredTheaters.Read(exINI, pSection, "Prerequisite.RequiredTheaters"); + + char* key = "Prerequisite.RequiredTheaters"; + char* context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + int index = Theater::FindIndex(cur); + if (index != -1) + Prerequisite_RequiredTheaters.push_back(index); + } + + key = nullptr; + + // Prerequisite with Generic Prerequistes support. + // Note: I have no idea of what could happen in the game engine if I push the negative indexes directly into the original Prerequisite tag... for that reason I duplicate this tag + key = "Prerequisite"; + context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + int idx = TechnoTypeClass::FindIndex(cur); + if (idx >= 0) + { + Prerequisite.push_back(idx); + } + else + { + int index = HouseExt::FindGenericPrerequisite(cur); + if (index < 0) + Prerequisite.push_back(index); + } + } + + key = nullptr; + + // Prerequisite.Negative with Generic Prerequistes support + key = "Prerequisite.Negative"; + context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + int idx = TechnoTypeClass::FindIndex(cur); + if (idx >= 0) + { + Prerequisite_Negative.push_back(idx); + } + else + { + int index = HouseExt::FindGenericPrerequisite(cur); + if (index < 0) + Prerequisite_Negative.push_back(index); + } + } + + key = nullptr; + + // Prerequisite.ListX with Generic Prerequistes support + this->Prerequisite_Lists.Read(exINI, pSection, "Prerequisite.Lists"); + + if (Prerequisite_Lists.Get() > 0) + { + for (int i = 1; i <= Prerequisite_Lists.Get(); i++) + { + char keySection[32]; + _snprintf_s(keySection, sizeof(keySection), "Prerequisite.List%d", i); + + DynamicVectorClass objectsList; + char* context2 = nullptr; + pINI->ReadString(pSection, keySection, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context2); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context2)) + { + int idx = TechnoTypeClass::FindIndex(cur); + if (idx >= 0) + { + objectsList.AddItem(idx); + } + else + { + int index = HouseExt::FindGenericPrerequisite(cur); + if (index < 0) + objectsList.AddItem(index); + } + } + + Prerequisite_ListVector.push_back(objectsList); + objectsList.Clear(); + } + } } template @@ -393,6 +489,12 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->IronCurtain_KeptOnDeploy) .Process(this->Explodes_KillPassengers) + + .Process(this->Prerequisite_RequiredTheaters) + .Process(this->Prerequisite) + .Process(this->Prerequisite_Negative) + .Process(this->Prerequisite_Lists) + .Process(this->Prerequisite_ListVector) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 0e01c3e79a..3ebfb6bcf5 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -158,6 +158,13 @@ class TechnoTypeExt std::vector> DeployedWeaponBurstFLHs; std::vector> EliteDeployedWeaponBurstFLHs; + // Ares 0.1 + ValueableVector Prerequisite_RequiredTheaters; + ValueableVector Prerequisite; + ValueableVector Prerequisite_Negative; + Valueable Prerequisite_Lists; + std::vector> Prerequisite_ListVector; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , UIDescription {} @@ -267,6 +274,9 @@ class TechnoTypeExt , DeployedSecondaryFireFLH { } , IronCurtain_KeptOnDeploy{ } , Explodes_KillPassengers { true } + , Prerequisite { } + , Prerequisite_Negative { } + , Prerequisite_Lists { 0 } { } virtual ~ExtData() = default; diff --git a/src/Phobos.Ext.cpp b/src/Phobos.Ext.cpp index 0dcaed68d2..b27037a8ea 100644 --- a/src/Phobos.Ext.cpp +++ b/src/Phobos.Ext.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,7 @@ auto MassActions = MassAction < BulletExt, BulletTypeExt, HouseExt, + HouseTypeExt, RadSiteExt, RulesExt, ScenarioExt, From bb8171ecf24bf1cdceacf6384d99a438682f6d25 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 2 Dec 2022 06:34:27 +0100 Subject: [PATCH 02/34] I forgot to merge the unclassified category at the end of the analysis If set this option. --- src/Ext/Team/Body.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 4e514c6679..64f33104b8 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -142,6 +142,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) bool splitTriggersByCategory = RulesExt::Global()->NewTeamsSelector_SplitTriggersByCategory; bool isFallbackEnabled = RulesExt::Global()->NewTeamsSelector_EnableFallback; teamCategory validCategory = teamCategory::None; + int mergeUnclassifiedCategoryWith = -1; double percentageUnclassifiedTriggers = 0.0; double percentageGroundTriggers = 0.0; @@ -180,8 +181,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) percentageUnclassifiedTriggers = 0.0; } - // TO-DO: HERE load new values from Rulesmd.ini, if set - percentageUnclassifiedTriggers = percentageUnclassifiedTriggers < 0.0 || percentageUnclassifiedTriggers > 1.0 ? 0.0 : percentageUnclassifiedTriggers; percentageGroundTriggers = percentageGroundTriggers < 0.0 || percentageGroundTriggers > 1.0 ? 0.0 : percentageGroundTriggers; percentageNavalTriggers = percentageNavalTriggers < 0.0 || percentageNavalTriggers > 1.0 ? 0.0 : percentageNavalTriggers; @@ -585,8 +584,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { // If they team has mixed members there is no need to continue + // Also, if the category was merged into another one if (teamIsCategory == teamCategory::Unclassified) + { + if (mergeUnclassifiedCategoryWith >= 0) + teamIsCategory = (teamCategory)mergeUnclassifiedCategoryWith; + break; + } if (entry.Amount > 0) { From 66b8a55fa6ae7d050acfae954dc6b0d22834974c Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 12:22:11 +0100 Subject: [PATCH 03/34] Cleaned code And removed comments and commented old code --- src/Ext/House/Body.cpp | 62 +++++------------------- src/Ext/House/Body.h | 11 ----- src/Ext/HouseType/Body.cpp | 1 + src/Ext/Rules/Body.cpp | 7 +-- src/Ext/Team/Body.cpp | 96 ++++++++++++------------------------- src/Ext/TechnoType/Body.cpp | 4 +- 6 files changed, 49 insertions(+), 132 deletions(-) diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 817c160468..71a49380ed 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -103,8 +103,12 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const for (auto pTechno : *TechnoClass::Array) { - if (pTechno->Owner == pThis && pTechno->GetTechnoType() == pItem && pTechno->IsAlive && pTechno->Health > 0) + if (pTechno->Owner == pThis + && pTechno->GetTechnoType() == pItem + && pTechno->IsAlive && pTechno->Health > 0) + { nInstances++; + } } if (nInstances >= pItem->BuildLimit) @@ -112,7 +116,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const bool prerequisiteNegativeMet = false; // Only one coincidence is needed - // Ares Prerequisite.Negative list. + // Ares Prerequisite.Negative list if (pItemExt->Prerequisite_Negative.size() > 0) { for (int idx : pItemExt->Prerequisite_Negative) @@ -120,7 +124,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (prerequisiteNegativeMet) return false; - if (idx < 0) // Is even possible this case? I have to check it + if (idx < 0) // Can be used generic prerequisites in this Ares tag? I have to investigate it but for now we support it... { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); @@ -139,13 +143,10 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } } - //ValueableVector prerequisite = pItemExt->Prerequisite; DynamicVectorClass prerequisiteOverride = pItem->PrerequisiteOverride; - //UnitTypeClass* prerequisiteProcAlternate = RulesClass::Instance->PrerequisiteProcAlternate; - - bool prerequisiteMet = false; // All in this list must appear in ownedBuildingTypes - bool prerequisiteOverrideMet = false; // Only one coincidence is needed + bool prerequisiteMet = false; // All buildings must appear in the buildings list owner by the house + bool prerequisiteOverrideMet = false; // This tag uses an OR comparator: Only one coincidence is needed if (prerequisiteOverride.Count > 0) { @@ -176,11 +177,11 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (pItemExt->Prerequisite.size() > 0) { bool found = false; - //if (pItem) Debug::Log("[%s] prereq check????\n", pItem->ID); + for (int idx : pItemExt->Prerequisite) { found = false; - //Debug::Log("\tidx: %d:\n",idx); + if (idx < 0) { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... @@ -188,7 +189,6 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } else { - //Debug::Log("\t[%s] (idx: %d):\n", TechnoTypeClass::Array->GetItem(idx)->ID, idx); for (auto pObject : ownedBuildingTypes) { if (found) @@ -196,8 +196,6 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (idx == pObject->ArrayIndex) found = true; - - //Debug::Log("\t\t[%s] (idx: %d) Found? %d\n", pObject->ID, pObject->ArrayIndex, found); } } @@ -209,6 +207,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } else { + // No prerequisites list means that always is buildable prerequisiteMet = true; } @@ -253,7 +252,6 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const prerequisiteListsMet = found; } - //Debug::Log("prerequisiteMet: %d, prerequisiteListsMet: %d, prerequisiteOverrideMet: %d\n", prerequisiteMet, prerequisiteListsMet, prerequisiteOverrideMet); return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; } @@ -264,14 +262,6 @@ bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); - /*Debug::Log("DEBUG: SELECTED DEFAULT PREREQUISITES: %s [%d]\n", RulesExt::Global()->GenericPrerequisitesNames.GetItem(std::abs(idx)), idx); - for (int i : selectedPrerequisite) - { - auto aaa = TechnoTypeClass::Array->GetItem(i); - Debug::Log("[%s] -> %d\n", aaa->ID, i); - } - Debug::Log("\n"); - */ if (selectedPrerequisite.Count == 0) return false; @@ -291,7 +281,6 @@ bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClass found: %d\n", idx, found); return found; } @@ -302,7 +291,7 @@ int HouseExt::FindGenericPrerequisite(const char* id) return 0; if (RulesExt::Global()->GenericPrerequisitesNames.Count == 0) - RulesExt::FillDefaultPrerequisites(); + RulesExt::FillDefaultPrerequisites(); // needed! int i = 0; for (auto str : RulesExt::Global()->GenericPrerequisitesNames) @@ -330,26 +319,6 @@ void HouseExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) for (size_t i = 0; i < 3; i++) this->RepairBaseNodes[i] = readBaseNodeRepairInfo[i < nWritten ? i : nWritten - 1]; } - - Valueable readNewTeamsSelector_MergeUnclassifiedCategoryWith; - readNewTeamsSelector_MergeUnclassifiedCategoryWith.Read(exINI, pSection, "NewTeamsSelector.MergeUnclassifiedCategoryWith"); - this->NewTeamsSelector_MergeUnclassifiedCategoryWith = readNewTeamsSelector_MergeUnclassifiedCategoryWith.Get(); - - Valueable readNewTeamsSelector_UnclassifiedCategoryPercentage; - readNewTeamsSelector_UnclassifiedCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.UnclassifiedCategoryPercentage"); - this->NewTeamsSelector_UnclassifiedCategoryPercentage = readNewTeamsSelector_UnclassifiedCategoryPercentage.Get(); - - Valueable readNewTeamsSelector_GroundCategoryPercentage; - readNewTeamsSelector_GroundCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.GroundCategoryPercentage"); - this->NewTeamsSelector_GroundCategoryPercentage = readNewTeamsSelector_GroundCategoryPercentage.Get(); - - Valueable readNewTeamsSelector_NavalCategoryPercentage; - readNewTeamsSelector_NavalCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.NavalCategoryPercentage"); - this->NewTeamsSelector_NavalCategoryPercentage = readNewTeamsSelector_NavalCategoryPercentage.Get(); - - Valueable readNewTeamsSelector_AirCategoryPercentage; - readNewTeamsSelector_AirCategoryPercentage.Read(exINI, pSection, "NewTeamsSelector.AirCategoryPercentage"); - this->NewTeamsSelector_AirCategoryPercentage = readNewTeamsSelector_AirCategoryPercentage.Get(); } @@ -367,11 +336,6 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->Factory_NavyType) .Process(this->Factory_AircraftType) .Process(this->RepairBaseNodes) - .Process(this->NewTeamsSelector_MergeUnclassifiedCategoryWith) - .Process(this->NewTeamsSelector_UnclassifiedCategoryPercentage) - .Process(this->NewTeamsSelector_GroundCategoryPercentage) - .Process(this->NewTeamsSelector_NavalCategoryPercentage) - .Process(this->NewTeamsSelector_AirCategoryPercentage) ; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 0faaaed6cb..9bf1ef4649 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -25,12 +25,6 @@ class HouseExt BuildingClass* Factory_NavyType; BuildingClass* Factory_AircraftType; - int NewTeamsSelector_MergeUnclassifiedCategoryWith; - double NewTeamsSelector_UnclassifiedCategoryPercentage; - double NewTeamsSelector_GroundCategoryPercentage; - double NewTeamsSelector_NavalCategoryPercentage; - double NewTeamsSelector_AirCategoryPercentage; - //Read from INI bool RepairBaseNodes[3]; @@ -42,11 +36,6 @@ class HouseExt , Factory_NavyType { nullptr } , Factory_AircraftType { nullptr } , RepairBaseNodes { false,false,false } - , NewTeamsSelector_MergeUnclassifiedCategoryWith { -1 } - , NewTeamsSelector_UnclassifiedCategoryPercentage { 0.25 } - , NewTeamsSelector_GroundCategoryPercentage { 0.25 } - , NewTeamsSelector_NavalCategoryPercentage { 0.25 } - , NewTeamsSelector_AirCategoryPercentage { 0.25 } { } virtual ~ExtData() = default; diff --git a/src/Ext/HouseType/Body.cpp b/src/Ext/HouseType/Body.cpp index abcb0c43b2..9dfeccf13b 100644 --- a/src/Ext/HouseType/Body.cpp +++ b/src/Ext/HouseType/Body.cpp @@ -74,6 +74,7 @@ bool HouseTypeExt::SaveGlobals(PhobosStreamWriter& Stm) { return Stm.Success(); } + // ============================= // container diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index abf2e55b9c..d41a8d0a5d 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -198,11 +198,11 @@ void RulesExt::FillDefaultPrerequisites() { if (RulesExt::Global()->GenericPrerequisitesNames.Count != 0) return; - //RulesExt::Global()->GenericPrerequisitesNames.Clear(); CCINIClass::INI_Rules; DynamicVectorClass empty; - RulesExt::Global()->GenericPrerequisitesNames.AddItem("POWER"); // -1 + + RulesExt::Global()->GenericPrerequisitesNames.AddItem("POWER"); // Official index: -1 RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisitePower); RulesExt::Global()->GenericPrerequisitesNames.AddItem("FACTORY"); // -2 RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisiteFactory); @@ -218,6 +218,7 @@ void RulesExt::FillDefaultPrerequisites() // If [GenericPrerequisites] is present will be added after these. // Also the originals can be replaced by new ones int genericPreqsCount = CCINIClass::INI_Rules->GetKeyCount("GenericPrerequisites"); + for (int i = 0; i < genericPreqsCount; ++i) { DynamicVectorClass objectsList; @@ -240,7 +241,7 @@ void RulesExt::FillDefaultPrerequisites() } else { - // New + // New generic prerequisite RulesExt::Global()->GenericPrerequisitesNames.AddItem(name); RulesExt::Global()->GenericPrerequisites.AddItem(objectsList); } diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 64f33104b8..9118cbbd08 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -97,16 +97,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) GET(HouseClass*, pHouse, ESI); bool houseIsHuman = pHouse->IsHumanPlayer; + if (SessionClass::IsCampaign()) houseIsHuman = pHouse->IsHumanPlayer || pHouse->IsInPlayerControl; if (houseIsHuman || pHouse->Type->MultiplayPassive) return SkipCode; - auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - if (!pHouseExt) - return SkipCode; - auto pHouseTypeExt = HouseTypeExt::ExtMap.Find(pHouse->Type); if (!pHouseTypeExt) return SkipCode; @@ -131,12 +128,10 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) DynamicVectorClass validTriggerCandidatesNavalOnly; DynamicVectorClass validTriggerCandidatesAirOnly; DynamicVectorClass validTriggerCandidatesUnclassifiedOnly; - int dice = ScenarioClass::Instance->Random.RandomRanged(1, 100); - //if (pHouse->IsHumanPlayer || pHouse->IsInPlayerControl) - //return ContinueFlow; + int dice = ScenarioClass::Instance->Random.RandomRanged(1, 100); - //This house must have enabled the triggers + // This house must have the triggers enabled if (dice <= pHouse->RatioAITriggerTeam && pHouse->AITriggersActive) { bool splitTriggersByCategory = RulesExt::Global()->NewTeamsSelector_SplitTriggersByCategory; @@ -151,7 +146,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (splitTriggersByCategory) { - int mergeUnclassifiedCategoryWith = pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.isset() ? pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.Get() : RulesExt::Global()->NewTeamsSelector_MergeUnclassifiedCategoryWith; + mergeUnclassifiedCategoryWith = pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.isset() ? pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.Get() : RulesExt::Global()->NewTeamsSelector_MergeUnclassifiedCategoryWith; // Should mixed teams be merged into another category? percentageUnclassifiedTriggers = pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_UnclassifiedCategoryPercentage; // Mixed teams percentageGroundTriggers = pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_GroundCategoryPercentage; // Only ground percentageNavalTriggers = pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_NavalCategoryPercentage; // Only Naval=yes @@ -187,6 +182,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) percentageAirTriggers = percentageAirTriggers < 0.0 || percentageAirTriggers > 1.0 ? 0.0 : percentageAirTriggers; double totalPercengates = percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers; + if (totalPercengates > 1.0 || totalPercengates <= 0.0) splitTriggersByCategory = false; // Note: if the sum of all percentages is less than 100% then that empty space will work like "no categories" @@ -222,13 +218,9 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } } - // PREPARING... - int houseIdx = pHouse->ArrayIndex; int sideIdx = pHouse->SideIndex + 1; - //int enemyHouseIndex = pHouse->EnemyHouseIndex >= 0 ? pHouse->EnemyHouseIndex : -1; auto houseDifficulty = pHouse->AIDifficulty; - //int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); int activeDefenseTeamsCount = 0; int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); @@ -256,20 +248,21 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } activeTeams = activeTeamsList.Count; - // We will use these for discarding triggers + + // We will use these values for discarding triggers bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; if (hasReachedMaxDefensiveTeamsLimit) - Debug::Log("DEBUG: House [%s] (Idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("DEBUG: House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); if (hasReachedMaxTeamsLimit) { - Debug::Log("DEBUG: House [%s] (Idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("DEBUG: House [%s] (idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } - // Build a list of built structures by the house + // Obtain the real list of structures the house have DynamicVectorClass ownedBuildingTypes; for (auto building : pHouse->Buildings) { @@ -334,14 +327,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } } - //Debug::Log("List of recruitable units of this house:\n"); - //for (auto item : recruitableUnits) - //{ - //if (item.object) - //Debug::Log("[%s] = %d\n", item.object->ID, item.count); - //} - //Debug::Log("AAA\n"); - HouseClass* targetHouse = nullptr; if (pHouse->EnemyHouseIndex >= 0) targetHouse = HouseClass::Array->GetItem(pHouse->EnemyHouseIndex); @@ -363,7 +348,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) // The trigger must be compatible with the owner if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) { - // "ConditionType=-1" will be skipped, is always valid (and save CPU) + // "ConditionType=-1" will be skipped, always is valid if ((int)pTrigger->ConditionType >= 0) { if ((int)pTrigger->ConditionType == 0) @@ -407,7 +392,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 8) { - // Simulate case 0: "enemy owns" but without restrict it against only 1 enemy house + // Simulate case 0: "enemy owns" but instead of restrict it against the main enemy house it is done against all enemies if (!pTrigger->ConditionObject) continue; @@ -423,7 +408,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); @@ -436,7 +421,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); @@ -449,7 +434,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); @@ -462,7 +447,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); @@ -475,7 +460,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); @@ -488,7 +473,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 14: Like in case 9 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); @@ -501,7 +486,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 15: Like in case 10 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); @@ -514,7 +499,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 16: Like in case 11 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); @@ -527,7 +512,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF and 256 is 0x0001 + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); @@ -538,18 +523,12 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) else { // Other cases from vanilla game - //bool cm = pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit); - //Debug::Log("[%s], \"%s\"\nOld ConditionMet section (%d, %s): %d [-, %d, %d]\n\n", pTrigger->ID, pTrigger->Team1->Name, (int)pTrigger->ConditionType, (pTrigger->ConditionObject ? pTrigger->ConditionObject->ID : ""), cm, pTrigger->Conditions->ComparatorOperand, pTrigger->Conditions->ComparatorType); - if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) continue; } } - /*else - { - bool cm = false; - }*/ + // All triggers below 5000 in current weight will get discarded if this mode is enabled if (onlyCheckImportantTriggers) { if (pTrigger->Weight_Current < 5000) @@ -595,39 +574,24 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (entry.Amount > 0) { - /* - What category goes each unit? there is the table: - - - Air category magic words: - ConsideredAircraft=yes - JumpJet=yes - BalloonHover=yes - - - Maval category magic words: - Naval=yes - MovementRestrictedTo=Water - - - Mixed category - MovementZone=Amphibious - - - - Ground category magic words: - Anyone that doesn't meet the previous magic words - */ - if (entry.Type) { - if (entry.Type->WhatAmI() == AbstractType::AircraftType || entry.Type->ConsideredAircraft) + if (entry.Type->WhatAmI() == AbstractType::AircraftType + || entry.Type->ConsideredAircraft) { // For now the team is from air category teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Air ? teamCategory::Air : teamCategory::Unclassified; } - else if (entry.Type->Naval && (entry.Type->MovementZone != MovementZone::Amphibious && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer || entry.Type->MovementZone != MovementZone::AmphibiousCrusher)) + else if (entry.Type->Naval + && (entry.Type->MovementZone != MovementZone::Amphibious + && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer + && entry.Type->MovementZone != MovementZone::AmphibiousCrusher)) { // For now the team is from naval category teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Naval ? teamCategory::Naval : teamCategory::Unclassified; } - else if (teamIsCategory != teamCategory::Naval && teamIsCategory != teamCategory::Air) + else if (teamIsCategory != teamCategory::Naval + && teamIsCategory != teamCategory::Air) { // For now the team doesn't belong to the previous categories teamIsCategory = teamIsCategory != teamCategory::Unclassified ? teamCategory::Ground : teamCategory::Unclassified; diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 749f40db53..6a2fff6a98 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -274,8 +274,6 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DeployedSecondaryFireFLH.Read(exArtINI, pArtSection, "DeployedSecondaryFireFLH"); // Prerequisite.RequiredTheaters contains a list of theader names - this->Prerequisite_RequiredTheaters.Read(exINI, pSection, "Prerequisite.RequiredTheaters"); - char* key = "Prerequisite.RequiredTheaters"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -290,7 +288,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite with Generic Prerequistes support. - // Note: I have no idea of what could happen in the game engine if I push the negative indexes directly into the original Prerequisite tag... for that reason I duplicate this tag + // Note: I have no idea of what could happen in all the game engine logics if I push the negative indexes of the Ares generic prerequisites directly into the original Prerequisite tag... for that reason this tag is duplicated for working with it key = "Prerequisite"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); From 355c682e02165a9f566717fbd53bf53bb8c43986 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 12:40:24 +0100 Subject: [PATCH 04/34] . --- src/Ext/Team/Body.cpp | 129 +++++++----------------------------------- 1 file changed, 22 insertions(+), 107 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 9118cbbd08..38b3a3de5c 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -182,11 +182,10 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) percentageAirTriggers = percentageAirTriggers < 0.0 || percentageAirTriggers > 1.0 ? 0.0 : percentageAirTriggers; double totalPercengates = percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers; - if (totalPercengates > 1.0 || totalPercengates <= 0.0) splitTriggersByCategory = false; - // Note: if the sum of all percentages is less than 100% then that empty space will work like "no categories" + // Note: if the sum of all percentages is less than 100% then that empty space will work like "no categories" if (splitTriggersByCategory) { int categoryDice = ScenarioClass::Instance->Random.RandomRanged(1, 100); @@ -220,7 +219,9 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int houseIdx = pHouse->ArrayIndex; int sideIdx = pHouse->SideIndex + 1; + //int enemyHouseIndex = pHouse->EnemyHouseIndex >= 0 ? pHouse->EnemyHouseIndex : -1; auto houseDifficulty = pHouse->AIDifficulty; + //int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); int activeDefenseTeamsCount = 0; int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); @@ -522,7 +523,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else { - // Other cases from vanilla game if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) continue; } @@ -562,8 +562,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { - // If they team has mixed members there is no need to continue - // Also, if the category was merged into another one + // If the team have mixed members there is no need to continue if (teamIsCategory == teamCategory::Unclassified) { if (mergeUnclassifiedCategoryWith >= 0) @@ -606,7 +605,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } bool allObjectsCanBeBuiltOrRecruited = true; - //Debug::Log("[%s] [%s] is evaluating unit prerequisites...\n", pTrigger->ID, pTriggerTeam1Type->ID); if (pTriggerTeam1Type->Autocreate) { @@ -619,21 +617,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; TechnoTypeClass* object = entry.Type; - //bool canBeBuilt = pHouse->AllPrerequisitesAvailable(object, ownedBuildingTypes, ownedBuildingTypes.Count); // Old - - // Experimental bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildingTypes); if (!canBeBuilt) { - //Debug::Log("[%s] not ok...\n", object->ID); allObjectsCanBeBuiltOrRecruited = false; break; } - else - { - //Debug::Log("[%s] build prerequisites met!\n", object->ID); - } } else { @@ -646,20 +636,16 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) allObjectsCanBeBuiltOrRecruited = false; } - //if (allObjectsCanBeBuiltOrRecruited) - //Debug::Log("[%s] [%s] can build units!...\n", pTrigger->ID, pTriggerTeam1Type->ID); - if (!allObjectsCanBeBuiltOrRecruited && pTriggerTeam1Type->Recruiter) { allObjectsCanBeBuiltOrRecruited = true; - //Debug::Log("[%s] [%s] checking if it can recruit...\n", pTrigger->ID, pTriggerTeam1Type->ID); + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { // Check if each unit in the taskforce has the available recruitable units in the map if (allObjectsCanBeBuiltOrRecruited && entry.Amount > 0) { bool canBeRecruited = false; - //Debug::Log("[%s] checking if there are recruitable...\n", entry.Type->ID); for (auto item : recruitableUnits) { @@ -674,14 +660,9 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!canBeRecruited) { - //Debug::Log("[%s] %d can not be recruited!\n", entry.Type->ID, entry.Amount); allObjectsCanBeBuiltOrRecruited = false; break; } - else - { - //Debug::Log("[%s] can recruit %d units!\n", entry.Type->ID, entry.Amount); - } } } } @@ -690,8 +671,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!allObjectsCanBeBuiltOrRecruited) continue; - // Special case: triggers become very important if they reach the max priority (value 5000) - // They get stored in a different list and all previous triggers are discarded. + // Special case: triggers become very important if they reach the max priority (value 5000). + // They get stored in a elitist list and all previous triggers are discarded. if (pTrigger->Weight_Current >= 5000 && !onlyCheckImportantTriggers) { // First time only @@ -706,14 +687,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) validCategory = teamCategory::None; } - // Reset and now only add important triggers to the list + // Reset the current ones and now only will be added important triggers to the list onlyCheckImportantTriggers = true; totalWeight = 0.0; splitTriggersByCategory = false; // VIP teams breaks the categories logic (on purpose) } // Passed all checks, save this trigger for later. - // The idea behind this is to simulate an ordered list of weights and once we throw the dice we'll know the winner trigger: The more weight means more possibilities to be selected. + // The idea behind this is to simulate an ordered list of weights and once we throw the dice we'll know the winner trigger: More weight means more possibilities to be selected. totalWeight += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; TriggerElementWeight item; item.Trigger = pTrigger; @@ -785,7 +766,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (splitTriggersByCategory) { - Debug::Log("DEBUG: Category percentages:\nMixed teams: %f\nGround teams: %f\nNaval teams: %f\nAir teams: %f\n", percentageUnclassifiedTriggers, percentageGroundTriggers, percentageNavalTriggers, percentageAirTriggers); + //Debug::Log("DEBUG: Category percentages:\nMixed teams: %f\nGround teams: %f\nNaval teams: %f\nAir teams: %f\n", percentageUnclassifiedTriggers, percentageGroundTriggers, percentageNavalTriggers, percentageAirTriggers); switch (validCategory) { @@ -817,17 +798,17 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) return SkipCode; } - if ((validCategory == teamCategory::Ground && totalGroundCategoryTriggers == 0) || - (validCategory == teamCategory::Unclassified && totalUnclassifiedCategoryTriggers == 0) || - (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) || - (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) + if ((validCategory == teamCategory::Ground && totalGroundCategoryTriggers == 0) + || (validCategory == teamCategory::Unclassified && totalUnclassifiedCategoryTriggers == 0) + || (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) + || (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) { - Debug::Log("DEBUG: [%s] (Idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); if (!isFallbackEnabled) return SkipCode; - Debug::Log("... BUT fallback mode is enabled so now it will check all triggers available.\n"); + Debug::Log("... BUT fallback mode is enabled so now will be checked all available triggers.\n"); validCategory = teamCategory::None; } @@ -840,163 +821,100 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { case teamCategory::None: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; - // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeight, weightDice); - // Debug::Log("Picking the trigger with highest weight from the list (No category set):\n"); for (auto element : validTriggerCandidates) { - // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); lastWeight = element.Weight; if (weightDice < element.Weight && !found) { - // Debug::Log(" ... CANDIDATE!\n"); selectedTrigger = element.Trigger; found = true; } - else - { - // Debug::Log(" ... \n"); - } } break; case teamCategory::Ground: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; - // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightGroundOnly, weightDice); - // Debug::Log("Picking the trigger with highest weight from the list (Ground category):\n"); for (auto element : validTriggerCandidatesGroundOnly) { - // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); lastWeight = element.Weight; if (weightDice < element.Weight && !found) { - // Debug::Log(" ... CANDIDATE!\n"); selectedTrigger = element.Trigger; found = true; } - else - { - // Debug::Log(" ... \n"); - } } break; case teamCategory::Unclassified: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; - // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightUnclassifiedOnly, weightDice); - // Debug::Log("Picking the trigger with highest weight from the list (Mixed category):\n"); for (auto element : validTriggerCandidatesUnclassifiedOnly) { - // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); lastWeight = element.Weight; if (weightDice < element.Weight && !found) { - // Debug::Log(" ... CANDIDATE!\n"); selectedTrigger = element.Trigger; found = true; } - else - { - // Debug::Log(" ... \n"); - } } break; case teamCategory::Naval: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; - // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightNavalOnly, weightDice); - // Debug::Log("Picking the trigger with highest weight from the list (Naval category):\n"); for (auto element : validTriggerCandidatesNavalOnly) { - // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); lastWeight = element.Weight; if (weightDice < element.Weight && !found) { - // Debug::Log(" ... CANDIDATE!\n"); selectedTrigger = element.Trigger; found = true; } - else - { - // Debug::Log(" ... \n"); - } } break; case teamCategory::Air: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; - // Debug::Log("Possible values of the dice [0 - %f]. Dice says: %f:\n", totalWeightAirOnly, weightDice); - // Debug::Log("Picking the trigger with highest weight from the list (Air category):\n"); for (auto element : validTriggerCandidatesAirOnly) { - // Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); lastWeight = element.Weight; if (weightDice < element.Weight && !found) { - // Debug::Log(" ... CANDIDATE!\n"); selectedTrigger = element.Trigger; found = true; } - else - { - // Debug::Log(" ... \n"); - } } break; default: break; } - /* - Debug::Log("[%s] Lottery range values: %f - %f", element.Trigger->ID, lastWeight, element.Weight); - lastWeight = element.Weight; - - if (weightDice < element.WeightOnlyNaval && !found) - { - Debug::Log(" ... CANDIDATE!\n"); - selectedTrigger = element.Trigger; - found = true; - } - else - { - Debug::Log(" ... \n"); - } - */ if (!selectedTrigger) { - Debug::Log("DEBUG: House [%s] (Idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("DEBUG: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } if (selectedTrigger->Weight_Current >= 5000.0 && selectedTrigger->Weight_Minimum <= 4999.0) { - // Next time this trigger will be out of the important triggers list + // Next time this trigger will be out of the elitist triggers list selectedTrigger->Weight_Current = 4999.0; } - // We have a winner - Debug::Log("DEBUG: House [%s] (Idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); - - //for (auto entry : selectedTrigger->Team1->TaskForce->Entries) - //{ - //if (entry.Amount > 0) - //Debug::Log("[%s] = %d\n", entry.Type->ID, entry.Amount); - //else - //break; - //} - //Debug::Log("\n"); + // We have a winner trigger here + Debug::Log("DEBUG: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); + // Team 1 creation auto pTriggerTeam1Type = selectedTrigger->Team1; if (pTriggerTeam1Type) { @@ -1015,6 +933,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } } + // Team 2 creation (if set) auto pTriggerTeam2Type = selectedTrigger->Team2; if (pTriggerTeam2Type) { @@ -1032,12 +951,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) newTeam->NeedsToDisappear = false; } } - - //Debug::Log("TeamClass::Array->Count: %d\n", TeamClass::Array->Count); } - - //selectedTeams46 = newTeamsList; return SkipCode; } From 5dc2862069d661d1f23753da2fe779d53aaea482 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 12:53:14 +0100 Subject: [PATCH 05/34] . --- src/Ext/Team/Body.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 38b3a3de5c..08f796bd36 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -219,9 +219,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int houseIdx = pHouse->ArrayIndex; int sideIdx = pHouse->SideIndex + 1; - //int enemyHouseIndex = pHouse->EnemyHouseIndex >= 0 ? pHouse->EnemyHouseIndex : -1; auto houseDifficulty = pHouse->AIDifficulty; - //int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); int activeDefenseTeamsCount = 0; int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); @@ -523,6 +521,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else { + // Other cases from vanilla game if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) continue; } @@ -672,7 +671,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; // Special case: triggers become very important if they reach the max priority (value 5000). - // They get stored in a elitist list and all previous triggers are discarded. + // They get stored in a elitist list and all previous triggers are discarded if (pTrigger->Weight_Current >= 5000 && !onlyCheckImportantTriggers) { // First time only @@ -683,7 +682,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) validTriggerCandidatesNavalOnly.Clear(); validTriggerCandidatesAirOnly.Clear(); validTriggerCandidatesUnclassifiedOnly.Clear(); - validCategory = teamCategory::None; } From ceb07ade06d9aeb073551c268b3c711cb60bbe46 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 13:10:04 +0100 Subject: [PATCH 06/34] . --- src/Ext/Team/Body.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 08f796bd36..d07c4e67e4 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -764,8 +764,6 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (splitTriggersByCategory) { - //Debug::Log("DEBUG: Category percentages:\nMixed teams: %f\nGround teams: %f\nNaval teams: %f\nAir teams: %f\n", percentageUnclassifiedTriggers, percentageGroundTriggers, percentageNavalTriggers, percentageAirTriggers); - switch (validCategory) { case teamCategory::Ground: From f1f43698505377edfdba6b6e3b247dbd4428e2a2 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 15:50:48 +0100 Subject: [PATCH 07/34] Added documentation --- docs/AI-Scripting-and-Mapping.md | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 2f0829e368..710bfcab73 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -17,6 +17,67 @@ RepairBaseNodes=no,no,no ; 3 booleans indicating whether AI repair basenodes in - Teams spawned by trigger action 7,80,107 can use IFV and opentopped logic normally. - `InitialPayload` logic from Ares is not supported yet. +## New AI Teams Selector + +- New AI system for selecting valid triggers in multiplayer randomly. Unlike the original method this one checks prerequisites and take care of other details. +- It can split the valid triggers into 4 categories: ground, air, naval & mixed categories. If set, AI picks a random trigger in a random category. +- Categories can have different chance probabilities. It can be set globally or customized per house. by default each category has a 25% chance to be selected. +- `NewTeamsSelector.MergeUnclassifiedCategoryWith` can be used for merging the mixed category (units are from different categories) into one of the main categories. +- In case of picking a category without valid triggers exist a fallback mode that allow picking a trigger from all valid triggers like if categories were disabled. +- if `Autocreate=yes` AI will care about all units prerequisites so if the house's tech tree is incomplete for the trigger it gets discarded. It understand Ares tags like `Prerequisite.RequiredTheaters`, `Prerequisite.Negative`, `Prerequisite.Lists` & `Generic prerequisites` section. +- If it finds a trigger with 5000 current probability weight then discard valid triggers all and start searching all valid triggers with weight 5000. AI will pick 1 randomly and decrease by 1 the current weight of the selected trigger (so if nothing happens in the next teams selection loop it won't appear in this special list). Under this scenario categories are disabled. + +In `rulesmd.ini`: +```ini +[AI] +NewTeamsSelector=false ; boolean +NewTeamsSelector.SplitTriggersByCategory=true ; boolean +NewTeamsSelector.EnableFallback=false ; boolean +NewTeamsSelector.GroundCategoryPercentage=0.25 ; floating point value, percents or absolute +NewTeamsSelector.NavalCategoryPercentage=0.25 ; floating point value, percents or absolute +NewTeamsSelector.AirCategoryPercentage=0.25 ; floating point value, percents or absolute +NewTeamsSelector.UnclassifiedCategoryPercentage=0.25 ; floating point value, percents or absolute +NewTeamsSelector.MergeUnclassifiedCategoryWith=-1 ; Integer - Ground: 1, Air: 2, Naval: 3 + +[SOMEHOUSE] ; HouseType +NewTeamsSelector.MergeUnclassifiedCategoryWith= ; boolean +NewTeamsSelector.UnclassifiedCategoryPercentage= ; floating point value, percents or absolute +NewTeamsSelector.GroundCategoryPercentage= ; floating point value, percents or absolute +NewTeamsSelector.AirCategoryPercentage ; floating point value, percents or absolute +NewTeamsSelector.NavalCategoryPercentage ; floating point value, percents or absolute +``` + +- Modified slightly AI Trigger conditions 0 (enemy owns ...), 1 (house owns ...) and 7 (civilian owns ...) and added 10 new conditions (from 8 to 17) that check lists of objects instead of only 1 unit. The first 3 were modified because the objects counters weren't updated in real-time. The possible modified and new cases are: + +| *Condition* | *List of objects* | *Description* | +| :---------: | :---------------: | :---------------------------------------------: | +| 0 (changed) | No | Count objects of the main enemy. If AI house doesn't have a main enemy It will search in all enemies. In vanilla YR if house doesn't have a main enemy this condition fail | +| 1 (changed) | No | Count own objects | +| 7 (changed) | No | Count civilian objects | +| 8 | No | Count enemy objects. It will search in all enemies | +| 9 | Yes | Count enemy objects. If AI house doesn't have a main enemy It will search in all enemies. | +| 10 | Yes | Count own objects | +| 11 | Yes | Count civilian objects | +| 12 | Yes | Count enemy objects. It will search in all enemies | +| 13 | Yes | Count objects from allies | +| 14 | Yes | Count enemy objects. If AI house doesn't have a main enemy It will search in all enemies. It uses `AND` comparator so each object must obey the same comparations | +| 15 | Yes | Count own objects. It uses `AND` comparator so each object must obey the same comparations | +| 16 | Yes | Count civilian objects. It uses `AND` comparator so each object must obey the same comparations | +| 17 | Yes | Count enemy objects. It will search in all enemies. It uses `AND` comparator so each object must obey the same comparations | + +- Some trigger conditions need to specify a 0-based list from `[AITargetTypes]`. The index of the list is written in hexadecimal and in little endian format. The value must be written at the end of the trigger: +`00000000000000000000000000000000000000000000000000000000AABBCCDD` +For example: list 0 is written 00 (put it in AA), list 1 is written 01 (put it in AA), list 10 is written 0A (put it in AA), 255 is 0xFF (put it in AA) and 256 is 0x0001 (put it in AABB), etc. +- The *`AITargetTypes` index#* values are obtained in the new `AITargetTypes` section that must be declared in `rulesmd.ini`: + +In `rulesmd.ini`: +```ini +[AITargetTypes] ; List of TechnoType lists +0=SOMETECHNOTYPE,SOMEOTHERTECHNOTYPE,SAMPLETECHNOTYPE +1=ANOTHERTECHNOTYPE,YETANOTHERTECHNOTYPE +; ... +``` + ## Script Actions ### `10000-10999` Ingame Actions From f0b9a561a3949e375233600a83d20671c4f724bf Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 16:06:18 +0100 Subject: [PATCH 08/34] What's new and credits file changes --- CREDITS.md | 1 + docs/Whats-New.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index bcc3e2e30e..83db04de96 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -118,6 +118,7 @@ This page lists all the individual contributions to the project by their author. - Shared ammo logic - Customizable FLH when infantry is prone or deployed - Initial strength for cloned infantry + - New AI teams selector - **Starkku**: - Warhead shield penetration & breaking - Strafing aircraft weapon customization diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 0b1ca16a30..682200a93f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -253,6 +253,7 @@ New: - Forcing specific weapon against cloaked or disguised targets (by Starkku) - Customizable ROF random delay (by Starkku) - Animation with `Tiled=yes` now supports `CustomPalette` (by ststl) +- New AI teams selector (by FS-21) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) From ee5ca9defae45f2e4eb93e29695c7f1c7bd96489 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 3 Dec 2022 16:14:29 +0100 Subject: [PATCH 09/34] trailing space --- docs/AI-Scripting-and-Mapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 710bfcab73..02cb910cc7 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -20,7 +20,7 @@ RepairBaseNodes=no,no,no ; 3 booleans indicating whether AI repair basenodes in ## New AI Teams Selector - New AI system for selecting valid triggers in multiplayer randomly. Unlike the original method this one checks prerequisites and take care of other details. -- It can split the valid triggers into 4 categories: ground, air, naval & mixed categories. If set, AI picks a random trigger in a random category. +- It can split the valid triggers into 4 categories: ground, air, naval & mixed categories. If set, AI picks a random trigger in a random category. - Categories can have different chance probabilities. It can be set globally or customized per house. by default each category has a 25% chance to be selected. - `NewTeamsSelector.MergeUnclassifiedCategoryWith` can be used for merging the mixed category (units are from different categories) into one of the main categories. - In case of picking a category without valid triggers exist a fallback mode that allow picking a trigger from all valid triggers like if categories were disabled. From 45b04569ad6e1527bdcc88e0d6c49fb321a20ddf Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 11 Jan 2023 07:11:11 +0100 Subject: [PATCH 10/34] Fixed all the bugs related to the Teams categorization, now all the AI teams go to the right category depending the members of the Taskforce. Added 2 new boolean tags for overwriting the categorization of some units: - ConsideredNaval - ConsideredVehicle --- src/Ext/Team/Body.cpp | 137 ++++++++++++++++++++++++++++-------- src/Ext/Team/Body.h | 1 + src/Ext/TechnoType/Body.cpp | 5 ++ src/Ext/TechnoType/Body.h | 5 ++ 4 files changed, 120 insertions(+), 28 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index d07c4e67e4..76698abb15 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -248,6 +248,26 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) activeTeams = activeTeamsList.Count; + /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); + for (auto team : activeTeamsList) + { + Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); + Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); + int i = 0; + + for (auto entry : team->Type->TaskForce->Entries) + { + if (entry.Amount > 0) + { + if (entry.Type) + Debug::Log("\t[%s]: %d / %d\n", entry.Type->ID, team->CountObjects[i], entry.Amount); + } + + i++; + } + } + Debug::Log("=====================\n");*/ + // We will use these values for discarding triggers bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; @@ -365,7 +385,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 1) { - // Simulate case 0: "house owns" + // Simulate case 1: "house owns" if (!pTrigger->ConditionObject) continue; @@ -521,7 +541,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else { - // Other cases from vanilla game + // Other cases from vanilla game if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) continue; } @@ -559,48 +579,69 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) // Analyze what kind of category is this main team if the feature is enabled if (splitTriggersByCategory) { - for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + //Debug::Log("DEBUG: TaskForce [%s] members:\n", pTriggerTeam1Type->TaskForce->ID); + // TaskForces are limited to 6 entries + for (int i = 0; i < 6; i++) { - // If the team have mixed members there is no need to continue - if (teamIsCategory == teamCategory::Unclassified) - { - if (mergeUnclassifiedCategoryWith >= 0) - teamIsCategory = (teamCategory)mergeUnclassifiedCategoryWith; - - break; - } + auto entry = pTriggerTeam1Type->TaskForce->Entries[i]; + teamCategory entryIsCategory = teamCategory::Ground; if (entry.Amount > 0) { - if (entry.Type) + if (entry.Type->WhatAmI() == AbstractType::AircraftType + || entry.Type->ConsideredAircraft) { - if (entry.Type->WhatAmI() == AbstractType::AircraftType - || entry.Type->ConsideredAircraft) - { - // For now the team is from air category - teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Air ? teamCategory::Air : teamCategory::Unclassified; - } - else if (entry.Type->Naval - && (entry.Type->MovementZone != MovementZone::Amphibious - && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer - && entry.Type->MovementZone != MovementZone::AmphibiousCrusher)) + // This unit is from air category + entryIsCategory = teamCategory::Air; + //Debug::Log("\t[%s](%d) is in AIR category.\n", entry.Type->ID, entry.Amount); + } + else + { + auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(entry.Type); + if (!pTechnoTypeExt) + continue; + + if (pTechnoTypeExt->ConsideredNaval + || (entry.Type->Naval + && (entry.Type->MovementZone != MovementZone::Amphibious + && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer + && entry.Type->MovementZone != MovementZone::AmphibiousCrusher))) { - // For now the team is from naval category - teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == teamCategory::Naval ? teamCategory::Naval : teamCategory::Unclassified; + // This unit is from naval category + entryIsCategory = teamCategory::Naval; + //Debug::Log("\t[%s](%d) is in NAVAL category.\n", entry.Type->ID, entry.Amount); } - else if (teamIsCategory != teamCategory::Naval - && teamIsCategory != teamCategory::Air) + + if (pTechnoTypeExt->ConsideredVehicle + || (entryIsCategory != teamCategory::Naval + && entryIsCategory != teamCategory::Air)) { - // For now the team doesn't belong to the previous categories - teamIsCategory = teamIsCategory != teamCategory::Unclassified ? teamCategory::Ground : teamCategory::Unclassified; + // This unit is from ground category + entryIsCategory = teamCategory::Ground; + //Debug::Log("\t[%s](%d) is in GROUND category.\n", entry.Type->ID, entry.Amount); } } + + // if a team have multiple categories it will be a mixed category + teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == entryIsCategory ? entryIsCategory : teamCategory::Unclassified; + + if (teamIsCategory == teamCategory::Unclassified) + break; } else { break; } } + + //Debug::Log("DEBUG: This team is a category %d (1:Ground, 2:Air, 3:Naval, 4:Mixed).\n", teamIsCategory); + // Si existe este valor y el team es MIXTO se sobreescribe el tipo de categoría + if (teamIsCategory == teamCategory::Unclassified + && mergeUnclassifiedCategoryWith >= 0) + { + //Debug::Log("DEBUG: MIXED category forced to work as category %d.\n", mergeUnclassifiedCategoryWith); + teamIsCategory = (teamCategory)mergeUnclassifiedCategoryWith; + } } bool allObjectsCanBeBuiltOrRecruited = true; @@ -817,6 +858,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { case teamCategory::None: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidates) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ for (auto element : validTriggerCandidates) { @@ -832,6 +881,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Ground: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesGroundOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ for (auto element : validTriggerCandidatesGroundOnly) { @@ -847,6 +904,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Unclassified: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesUnclassifiedOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ for (auto element : validTriggerCandidatesUnclassifiedOnly) { @@ -862,6 +927,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Naval: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesNavalOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ for (auto element : validTriggerCandidatesNavalOnly) { @@ -877,6 +950,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Air: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesAirOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ for (auto element : validTriggerCandidatesAirOnly) { diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index 559e39b185..89c9957248 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -9,6 +9,7 @@ #include #include #include +#include #include enum teamCategory diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 6a2fff6a98..e63078eb02 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -273,6 +273,9 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DeployedPrimaryFireFLH.Read(exArtINI, pArtSection, "DeployedPrimaryFireFLH"); this->DeployedSecondaryFireFLH.Read(exArtINI, pArtSection, "DeployedSecondaryFireFLH"); + this->ConsideredNaval.Read(exINI, pSection, "ConsideredNaval"); + this->ConsideredVehicle.Read(exINI, pSection, "ConsideredVehicle"); + // Prerequisite.RequiredTheaters contains a list of theader names char* key = "Prerequisite.RequiredTheaters"; char* context = nullptr; @@ -493,6 +496,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Prerequisite_Negative) .Process(this->Prerequisite_Lists) .Process(this->Prerequisite_ListVector) + .Process(this->ConsideredNaval) + .Process(this->ConsideredVehicle) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 3ebfb6bcf5..1951273b68 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -158,6 +158,9 @@ class TechnoTypeExt std::vector> DeployedWeaponBurstFLHs; std::vector> EliteDeployedWeaponBurstFLHs; + Nullable ConsideredNaval; + Nullable ConsideredVehicle; + // Ares 0.1 ValueableVector Prerequisite_RequiredTheaters; ValueableVector Prerequisite; @@ -277,6 +280,8 @@ class TechnoTypeExt , Prerequisite { } , Prerequisite_Negative { } , Prerequisite_Lists { 0 } + , ConsideredNaval { } + , ConsideredVehicle { } { } virtual ~ExtData() = default; From 9712713e27907f5911e080cb8af02a34aebba362 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 11 Jan 2023 16:32:16 +0100 Subject: [PATCH 11/34] Updated documentation --- docs/AI-Scripting-and-Mapping.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 02cb910cc7..1a323788b7 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -26,6 +26,7 @@ RepairBaseNodes=no,no,no ; 3 booleans indicating whether AI repair basenodes in - In case of picking a category without valid triggers exist a fallback mode that allow picking a trigger from all valid triggers like if categories were disabled. - if `Autocreate=yes` AI will care about all units prerequisites so if the house's tech tree is incomplete for the trigger it gets discarded. It understand Ares tags like `Prerequisite.RequiredTheaters`, `Prerequisite.Negative`, `Prerequisite.Lists` & `Generic prerequisites` section. - If it finds a trigger with 5000 current probability weight then discard valid triggers all and start searching all valid triggers with weight 5000. AI will pick 1 randomly and decrease by 1 the current weight of the selected trigger (so if nothing happens in the next teams selection loop it won't appear in this special list). Under this scenario categories are disabled. +- Units can override the category using `ConsideredVehicle` and `ConsideredNaval` boolean tags In `rulesmd.ini`: ```ini @@ -45,6 +46,10 @@ NewTeamsSelector.UnclassifiedCategoryPercentage= ; floating point value, percen NewTeamsSelector.GroundCategoryPercentage= ; floating point value, percents or absolute NewTeamsSelector.AirCategoryPercentage ; floating point value, percents or absolute NewTeamsSelector.NavalCategoryPercentage ; floating point value, percents or absolute + +[SOMETECHNO] ; TechnoType +ConsideredNaval= ; boolean +ConsideredVehicle= ; boolean ``` - Modified slightly AI Trigger conditions 0 (enemy owns ...), 1 (house owns ...) and 7 (civilian owns ...) and added 10 new conditions (from 8 to 17) that check lists of objects instead of only 1 unit. The first 3 were modified because the objects counters weren't updated in real-time. The possible modified and new cases are: From 08691d5aae3b56a9878019f21955cf7e1202da7d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 7 Feb 2023 06:49:03 +0100 Subject: [PATCH 12/34] Secret Lab support for the AI and bug fixes Now AI will be able to unlock secret lab units and use them in Teams. Other bugfixes and improvements. --- docs/AI-Scripting-and-Mapping.md | 3 +- src/Ext/Building/Body.cpp | 123 +++++++++++++++++++++++++++++++ src/Ext/Building/Body.h | 4 + src/Ext/Building/Hooks.cpp | 13 ++++ src/Ext/BuildingType/Body.cpp | 19 +++++ src/Ext/BuildingType/Body.h | 5 ++ src/Ext/House/Body.cpp | 22 +++++- src/Ext/House/Body.h | 2 +- src/Ext/Team/Body.cpp | 32 ++++---- src/Ext/TechnoType/Body.cpp | 44 +++++++++-- src/Ext/TechnoType/Body.h | 6 ++ 11 files changed, 247 insertions(+), 26 deletions(-) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 1a323788b7..64063ee263 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -26,7 +26,8 @@ RepairBaseNodes=no,no,no ; 3 booleans indicating whether AI repair basenodes in - In case of picking a category without valid triggers exist a fallback mode that allow picking a trigger from all valid triggers like if categories were disabled. - if `Autocreate=yes` AI will care about all units prerequisites so if the house's tech tree is incomplete for the trigger it gets discarded. It understand Ares tags like `Prerequisite.RequiredTheaters`, `Prerequisite.Negative`, `Prerequisite.Lists` & `Generic prerequisites` section. - If it finds a trigger with 5000 current probability weight then discard valid triggers all and start searching all valid triggers with weight 5000. AI will pick 1 randomly and decrease by 1 the current weight of the selected trigger (so if nothing happens in the next teams selection loop it won't appear in this special list). Under this scenario categories are disabled. -- Units can override the category using `ConsideredVehicle` and `ConsideredNaval` boolean tags +- Units can override the category using `ConsideredVehicle` and `ConsideredNaval` boolean tags. +- AI is be able to use unlocked units in captured Secret Labs. In `rulesmd.ini`: ```ini diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 835f771bf0..7f9cb682bc 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -326,6 +326,128 @@ bool BuildingExt::HandleInfiltrate(BuildingClass* pBuilding, HouseClass* pInfilt return true; } +// Assigns a secret production option to the AI building. +void BuildingExt::ExtData::UpdateSecretLabAI() +{ + auto pThis = this->OwnerObject(); + auto pOwner = pThis->Owner; + + if (!pOwner || pOwner->Type->MultiplayPassive || pOwner->IsControlledByHuman()) + return; + + auto pType = pThis->Type; + + // Fixed item, no need to randomize + if (pType->SecretInfantry || pType->SecretUnit || pType->SecretBuilding) + return; + + auto pDataType = BuildingTypeExt::ExtMap.Find(pType); + + // If Secret Lab already picked a techno and isn't allowed to recalculate it again the function ends + if (this->SecretLab_Placed && !pDataType->Secret_RecalcOnCapture) + return; + + DynamicVectorClass validCandidates; + DynamicVectorClass possibleCandidates; + + if (pDataType->PossibleBoons.size() > 0) + { + for (const auto& boon : pDataType->PossibleBoons) + { + possibleCandidates.AddItem(boon); + } + } + else + { + for (const auto& boon : RulesClass::Instance->SecretInfantry) + { + possibleCandidates.AddItem(boon); + } + + for (const auto& boon : RulesClass::Instance->SecretUnits) + { + possibleCandidates.AddItem(boon); + } + + for (const auto& boon : RulesClass::Instance->SecretBuildings) + { + possibleCandidates.AddItem(boon); + } + } + + if (possibleCandidates.Count > 0) + { + DynamicVectorClass ownedBuildingTypes; + + if (ownedBuildingTypes.Count == 0) + { + for (auto building : pOwner->Buildings) + { + ownedBuildingTypes.AddUnique(building->Type); + } + } + + for (const auto& boon : possibleCandidates) + { + auto pExt = TechnoTypeExt::ExtMap.Find(boon); + bool isRequiredHouse = true; // Default value if Secret.RequiredHouses isn't declared + + if (pExt->Secret_RequiredHouses.size() > 0) + isRequiredHouse = false; + + for (const auto houseId : pExt->Secret_RequiredHouses) + { + int houseIdx = HouseTypeClass::FindIndex(houseId.c_str()); + if (houseIdx < 0) + continue; + + if (pOwner->Type->ArrayIndex == houseIdx) + { + bool canBeBuilt = HouseExt::PrerequisitesMet(pOwner, boon, ownedBuildingTypes, true); + + if (canBeBuilt) + { + isRequiredHouse = true; + break; + } + } + } + + bool isForbiddenHouse = false; // Default value if Secret.ForbiddenHouses isn't declared + + for (const auto houseId : pExt->Secret_ForbiddenHouses) + { + int houseIdx = HouseTypeClass::FindIndex(houseId.c_str()); + if (houseIdx < 0) + continue; + + if (pOwner->Type->ArrayIndex == houseIdx) + { + isForbiddenHouse = true; + break; + } + } + + if (isRequiredHouse && !isForbiddenHouse) + validCandidates.AddItem(boon); + } + } + + // pick one of all eligible items + if (validCandidates.Count > 0) + { + auto result = validCandidates[ScenarioClass::Instance->Random.RandomRanged(0, validCandidates.Count - 1)]; + Debug::Log("[Secret Lab AI] rolled %s for %s\n", result->ID, pType->ID); + pThis->SecretProduction = result; + this->SecretLab_Placed = true; + } + else + { + Debug::Log("[Secret Lab AI] %s has no boons applicable to country [%s]!\n", + pType->ID, pOwner->Type->ID); + } +} + // ============================= // load / save @@ -339,6 +461,7 @@ void BuildingExt::ExtData::Serialize(T& Stm) .Process(this->GrindingWeapon_LastFiredFrame) .Process(this->CurrentAirFactory) .Process(this->AccumulatedGrindingRefund) + .Process(this->SecretLab_Placed) ; } diff --git a/src/Ext/Building/Body.h b/src/Ext/Building/Body.h index 0424bbd2a9..a30068e775 100644 --- a/src/Ext/Building/Body.h +++ b/src/Ext/Building/Body.h @@ -12,6 +12,7 @@ #include #include #include +#include class BuildingExt { @@ -27,6 +28,7 @@ class BuildingExt int GrindingWeapon_LastFiredFrame; BuildingClass* CurrentAirFactory; int AccumulatedGrindingRefund; + bool SecretLab_Placed; ExtData(BuildingClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } @@ -35,11 +37,13 @@ class BuildingExt , GrindingWeapon_LastFiredFrame { 0 } , CurrentAirFactory { nullptr } , AccumulatedGrindingRefund { 0 } + , SecretLab_Placed { false } { } void DisplayGrinderRefund(); void ApplyPoweredKillSpawns(); bool HasSuperWeapon(int index, bool withUpgrades) const; + void UpdateSecretLabAI(); virtual ~ExtData() = default; diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index 153240cc80..aa55a3c3e9 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -301,3 +301,16 @@ DEFINE_HOOK(0x4575A2, BuildingClass_Infiltrate_AfterAres, 0xE) return 0; } #undef INFILTRATE_HOOK_MAGIC + +DEFINE_HOOK(0x445F80, BuildingClass_GrandOpening_UpdateSecretLabAI, 0x5) +{ + GET(BuildingClass*, pThis, ECX); + + if (pThis->Type->SecretLab && !pThis->Owner->IsControlledByHuman()) + { + auto pExt = BuildingExt::ExtMap.Find(pThis); + pExt->UpdateSecretLabAI(); + } + + return 0; +} diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index eb6648ed09..a002bb902f 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -142,6 +142,23 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Grinding_DisplayRefund_Houses.Read(exINI, pSection, "Grinding.DisplayRefund.Houses"); this->Grinding_DisplayRefund_Offset.Read(exINI, pSection, "Grinding.DisplayRefund.Offset"); + this->Secret_RecalcOnCapture.Read(exINI, pSection, "SecretLab.GenerateOnCapture"); + //this->PossibleBoons.Read(exINI, pSection, "SecretLab.PossibleBoons"); + + // Secret.Boons contains a list of TechnoTypeClass IDs + char* key = "SecretLab.PossibleBoons"; + char* context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + int index = TechnoTypeClass::FindIndex(cur); + if (index != -1) + this->PossibleBoons.push_back(TechnoTypeClass::Array->GetItem(index)); + } + + key = nullptr; + // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); if (SuperWeaponTypeClass::Array->Count > 0) @@ -220,6 +237,8 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->SpyEffect_Custom) .Process(this->SpyEffect_VictimSuperWeapon) .Process(this->SpyEffect_InfiltratorSuperWeapon) + .Process(this->Secret_RecalcOnCapture) + .Process(this->PossibleBoons) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index d9a6a48032..5d43a63229 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -51,6 +51,9 @@ class BuildingTypeExt NullableIdx SpyEffect_VictimSuperWeapon; NullableIdx SpyEffect_InfiltratorSuperWeapon; + Valueable Secret_RecalcOnCapture; + NullableVector PossibleBoons; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -81,6 +84,8 @@ class BuildingTypeExt , SpyEffect_Custom { false } , SpyEffect_VictimSuperWeapon {} , SpyEffect_InfiltratorSuperWeapon {} + , Secret_RecalcOnCapture { false } + , PossibleBoons {} { } // Ares 0.A functions diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 71a49380ed..e26aed0847 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -73,7 +73,7 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa } } -bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes) +bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes, bool skipSecretLabChecks) { if (!pThis || !pItem) return false; @@ -86,6 +86,15 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (!pItemExt) return false; + // If the unit is available after capturing a SecretLab=yes must be evaluated if meets the prerequisite + if (!skipSecretLabChecks && pItemExt->ConsideredSecretLabTech && !pThis->HasFromSecretLab(pItem)) + return false; + + // Check if it appears in Owner=, RequiredHouses= and ForbiddenHouses= + // Note: if RequiredHouses = tag doesn't exist InRequiredHouses() always returns TRUE + if (!pThis->InOwners(pItem) || !pThis->InRequiredHouses(pItem) || pThis->InForbiddenHouses(pItem)) + return false; + // Prerequisite.RequiredTheaters check if (pItemExt->Prerequisite_RequiredTheaters.size() > 0) { @@ -121,9 +130,6 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const { for (int idx : pItemExt->Prerequisite_Negative) { - if (prerequisiteNegativeMet) - return false; - if (idx < 0) // Can be used generic prerequisites in this Ares tag? I have to investigate it but for now we support it... { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... @@ -140,9 +146,16 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const prerequisiteNegativeMet = true; } } + + if (prerequisiteNegativeMet) + return false; } } + // Main prerequisite checks are skipped if a new secret lab object is in process to be unlocked + if (skipSecretLabChecks) + return true; + DynamicVectorClass prerequisiteOverride = pItem->PrerequisiteOverride; bool prerequisiteMet = false; // All buildings must appear in the buildings list owner by the house @@ -261,6 +274,7 @@ bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); + //auto selectedPrerequisiteName = RulesExt::Global()->GenericPrerequisitesNames.GetItem(std::abs(idx));// Only used for easy debug if (selectedPrerequisite.Count == 0) return false; diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 9bf1ef4649..ce7d261784 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -74,7 +74,7 @@ class HouseExt static int ActiveHarvesterCount(HouseClass* pThis); static int TotalHarvesterCount(HouseClass* pThis); static HouseClass* GetHouseKind(OwnerHouseKind kind, bool allowRandom, HouseClass* pDefault, HouseClass* pInvoker = nullptr, HouseClass* pVictim = nullptr); - static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes); + static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes, bool skipSecretLabChecks); static bool HasGenericPrerequisite(int idx, const DynamicVectorClass ownedBuildingTypes); static int FindGenericPrerequisite(const char* id); }; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 76698abb15..be51dfbc17 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -185,33 +185,39 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (totalPercengates > 1.0 || totalPercengates <= 0.0) splitTriggersByCategory = false; - // Note: if the sum of all percentages is less than 100% then that empty space will work like "no categories" + if (splitTriggersByCategory) { int categoryDice = ScenarioClass::Instance->Random.RandomRanged(1, 100); + int unclassifiedValue = (int)(percentageUnclassifiedTriggers * 100.0); + int groundValue = (int)(percentageGroundTriggers * 100.0); + int airValue = (int)(percentageAirTriggers * 100.0); + int navalValue = (int)(percentageNavalTriggers * 100.0); - if (categoryDice == 0) - { - splitTriggersByCategory = false; - } - else if (categoryDice <= (int)(percentageUnclassifiedTriggers * 100.0)) + // Pick what type of team will be selected in this round + if (percentageUnclassifiedTriggers > 0.0 && categoryDice <= unclassifiedValue) { validCategory = teamCategory::Unclassified; + Debug::Log("New AI team category selection: dice %d <= %d (MIXED)\n", categoryDice, unclassifiedValue); } - else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers) * 100.0)) + else if (percentageGroundTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue)) { validCategory = teamCategory::Ground; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + GROUND: %d%%)\n", categoryDice, (unclassifiedValue + groundValue), unclassifiedValue, groundValue); } - else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers) * 100.0)) + else if (percentageAirTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue)) { - validCategory = teamCategory::Naval; + validCategory = teamCategory::Air; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + AIR: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue), unclassifiedValue, groundValue, airValue); } - else if (categoryDice <= (int)((percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers) * 100.0)) + else if (percentageNavalTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue + navalValue)) { - validCategory = teamCategory::Air; + validCategory = teamCategory::Naval; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + air: %d%% + NAVAL: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue + navalValue), unclassifiedValue, groundValue, airValue, navalValue); } else { + // If the sum of all percentages is less than 100% then that empty space will work like "no categories" splitTriggersByCategory = false; } } @@ -657,7 +663,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; TechnoTypeClass* object = entry.Type; - bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildingTypes); + bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildingTypes, false); if (!canBeBuilt) { @@ -845,7 +851,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!isFallbackEnabled) return SkipCode; - Debug::Log("... BUT fallback mode is enabled so now will be checked all available triggers.\n"); + Debug::Log("... but fallback mode is enabled so now will be checked all available triggers.\n"); validCategory = teamCategory::None; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index e63078eb02..40b2797fb8 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -275,17 +275,44 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ConsideredNaval.Read(exINI, pSection, "ConsideredNaval"); this->ConsideredVehicle.Read(exINI, pSection, "ConsideredVehicle"); + this->ConsideredSecretLabTech.Read(exINI, pSection, "ConsideredSecretLabTech"); - // Prerequisite.RequiredTheaters contains a list of theader names - char* key = "Prerequisite.RequiredTheaters"; + // Secret.RequiredHouses contains a list of HouseTypeClass indexes + char* key = "SecretLab.RequiredHouses"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + std::string item(cur); + this->Secret_RequiredHouses.push_back(item); + } + + key = nullptr; + + // Secret.ForbiddenHouses contains a list of HouseTypeClass indexes + key = "SecretLab.ForbiddenHouses"; + context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) + { + std::string item(cur); + this->Secret_ForbiddenHouses.push_back(item); + } + + key = nullptr; + + // Prerequisite.RequiredTheaters contains a list of theader names + key = "Prerequisite.RequiredTheaters"; + context = nullptr; + pINI->ReadString(pSection, key, "", Phobos::readBuffer); + for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { int index = Theater::FindIndex(cur); if (index != -1) - Prerequisite_RequiredTheaters.push_back(index); + this->Prerequisite_RequiredTheaters.push_back(index); } key = nullptr; @@ -301,13 +328,13 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) int idx = TechnoTypeClass::FindIndex(cur); if (idx >= 0) { - Prerequisite.push_back(idx); + this->Prerequisite.push_back(idx); } else { int index = HouseExt::FindGenericPrerequisite(cur); if (index < 0) - Prerequisite.push_back(index); + this->Prerequisite.push_back(index); } } @@ -323,13 +350,13 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) int idx = TechnoTypeClass::FindIndex(cur); if (idx >= 0) { - Prerequisite_Negative.push_back(idx); + this->Prerequisite_Negative.push_back(idx); } else { int index = HouseExt::FindGenericPrerequisite(cur); if (index < 0) - Prerequisite_Negative.push_back(index); + this->Prerequisite_Negative.push_back(index); } } @@ -498,6 +525,9 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Prerequisite_ListVector) .Process(this->ConsideredNaval) .Process(this->ConsideredVehicle) + .Process(this->ConsideredSecretLabTech) + .Process(this->Secret_RequiredHouses) + .Process(this->Secret_ForbiddenHouses) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 1951273b68..61fe193658 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -160,6 +160,9 @@ class TechnoTypeExt Nullable ConsideredNaval; Nullable ConsideredVehicle; + Valueable ConsideredSecretLabTech; + std::vector Secret_RequiredHouses; + std::vector Secret_ForbiddenHouses; // Ares 0.1 ValueableVector Prerequisite_RequiredTheaters; @@ -282,6 +285,9 @@ class TechnoTypeExt , Prerequisite_Lists { 0 } , ConsideredNaval { } , ConsideredVehicle { } + , ConsideredSecretLabTech { false } + , Secret_RequiredHouses { } + , Secret_ForbiddenHouses { } { } virtual ~ExtData() = default; From c2877737790d5d97c6d57513ea9b7466de22b5a7 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 8 Feb 2023 18:25:49 +0100 Subject: [PATCH 13/34] Point to the latest YRpp that can compile... --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index a41eb1a449..9db1fd96f1 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit a41eb1a4491f8256459a1c3e4a25af5ebded45db +Subproject commit 9db1fd96f1bf709c40c9cd304b276ed7578998ca From 57a948750d28e7960ea21c4dbe97171dcf523448 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 10 Feb 2023 19:35:59 +0100 Subject: [PATCH 14/34] Fix compilation after the develop merge --- src/Ext/Building/Body.cpp | 1 + src/Ext/Building/Body.h | 1 - src/Ext/BuildingType/Body.cpp | 2 +- src/Ext/Rules/Body.h | 2 +- src/Ext/Team/Body.cpp | 70 +++++++++++++++++------------------ src/Ext/Team/Body.h | 12 +++--- src/Ext/TechnoType/Body.cpp | 2 +- 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 8afd960452..8511964ccb 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -4,6 +4,7 @@ #include #include +#include template<> const DWORD Extension::Canary = 0x87654321; BuildingExt::ExtContainer BuildingExt::ExtMap; diff --git a/src/Ext/Building/Body.h b/src/Ext/Building/Body.h index 35788d8201..d5401f5141 100644 --- a/src/Ext/Building/Body.h +++ b/src/Ext/Building/Body.h @@ -12,7 +12,6 @@ #include #include #include -#include class BuildingExt { diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 350c8e4749..fb152f8837 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -147,7 +147,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) //this->PossibleBoons.Read(exINI, pSection, "SecretLab.PossibleBoons"); // Secret.Boons contains a list of TechnoTypeClass IDs - char* key = "SecretLab.PossibleBoons"; + const char* key = "SecretLab.PossibleBoons"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index b69df70c77..df467331a5 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -75,7 +75,7 @@ class RulesExt Valueable RadialIndicatorVisibility; DynamicVectorClass> GenericPrerequisites; - DynamicVectorClass GenericPrerequisitesNames; + DynamicVectorClass GenericPrerequisitesNames; Valueable NewTeamsSelector; Valueable NewTeamsSelector_SplitTriggersByCategory; diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index be51dfbc17..d165d0ce03 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -382,8 +382,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); if (!isConditionMet) @@ -395,8 +395,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -408,8 +408,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -421,8 +421,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -430,11 +430,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 9) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); if (!isConditionMet) @@ -443,11 +443,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 10) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -456,11 +456,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 11) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -469,11 +469,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 12) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -482,11 +482,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 13) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); if (!isConditionMet) @@ -495,11 +495,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 14) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 14: Like in case 9 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); if (!isConditionMet) @@ -508,11 +508,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 15) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 15: Like in case 10 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); if (!isConditionMet) @@ -521,11 +521,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 16) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 16: Like in case 11 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); if (!isConditionMet) @@ -534,11 +534,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 17) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); if (!isConditionMet) @@ -1039,7 +1039,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) return SkipCode; } -bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list) +bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list) { bool result = false; int counter = 0; @@ -1088,7 +1088,7 @@ bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool alli return result; } -bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list) +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) { bool result = false; int counter = 0; @@ -1141,7 +1141,7 @@ bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClas return result; } -bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) { bool result = false; int counter = 0; @@ -1195,11 +1195,11 @@ bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list) { bool result = true; - if (list.Count == 0) + if (list.size() == 0) return false; // Count all objects of the list, like an AND operator @@ -1251,14 +1251,14 @@ bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, Dynami return result; } -bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list) +bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list) { bool result = true; if (pEnemy && pHouse->IsAlliedWith(pEnemy)) pEnemy = nullptr; - if (list.Count == 0) + if (list.size() == 0) return false; // Count all objects of the list, like an AND operator @@ -1312,11 +1312,11 @@ bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseC return result; } -bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list) { bool result = true; - if (list.Count == 0) + if (list.size() == 0) return false; // Any neutral house should be capable to meet the prerequisites diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index 89c9957248..f6d06a12ff 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -144,11 +144,11 @@ class TeamExt static ExtContainer ExtMap; - static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list); - static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list); - static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list); - static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list); - static bool NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list); - static bool NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list); + static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list); + static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list); + static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list); + static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list); + static bool NeutralOwns(AITriggerTypeClass* pThis, std::vector list); + static bool NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list); }; diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 0b65900d22..e9bf0632d0 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -285,7 +285,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ConsideredSecretLabTech.Read(exINI, pSection, "ConsideredSecretLabTech"); // Secret.RequiredHouses contains a list of HouseTypeClass indexes - char* key = "SecretLab.RequiredHouses"; + const char* key = "SecretLab.RequiredHouses"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); From 035abe83556806209c09954be7cca0d7c6402b27 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 18 Feb 2023 11:53:00 +0100 Subject: [PATCH 15/34] crash fix --- src/Ext/Team/Body.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index d165d0ce03..ec09d79692 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -263,7 +263,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) for (auto entry : team->Type->TaskForce->Entries) { - if (entry.Amount > 0) + if (entry.Type && entry.Amount > 0) { if (entry.Type) Debug::Log("\t[%s]: %d / %d\n", entry.Type->ID, team->CountObjects[i], entry.Amount); @@ -594,6 +594,9 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (entry.Amount > 0) { + if (!entry.Type) + continue; + if (entry.Type->WhatAmI() == AbstractType::AircraftType || entry.Type->ConsideredAircraft) { @@ -689,7 +692,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { // Check if each unit in the taskforce has the available recruitable units in the map - if (allObjectsCanBeBuiltOrRecruited && entry.Amount > 0) + if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) { bool canBeRecruited = false; From 9cc658d5e224d4dd495434e4ec7a2cda92e122d7 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 26 May 2023 19:09:10 +0200 Subject: [PATCH 16/34] Code improvements and additions Trigger conditions 18 and 19 for checking Bridges status --- Phobos.vcxproj | 1 + docs/AI-Scripting-and-Mapping.md | 2 + src/Ext/Building/Body.cpp | 11 +- src/Ext/House/Body.cpp | 72 ++++------ src/Ext/House/Body.h | 6 +- src/Ext/Scenario/Hooks.cpp | 36 +++++ src/Ext/Team/Body.cpp | 226 +++++++++++++++++-------------- src/Ext/Team/Body.h | 13 +- 8 files changed, 200 insertions(+), 167 deletions(-) create mode 100644 src/Ext/Scenario/Hooks.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 62e4b8ca77..b54dbe9ded 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -33,6 +33,7 @@ + diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 0cc30fd74c..d516cacff3 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -71,6 +71,8 @@ ConsideredVehicle= ; boolean | 15 | Yes | Count own objects. It uses `AND` comparator so each object must obey the same comparations | | 16 | Yes | Count civilian objects. It uses `AND` comparator so each object must obey the same comparations | | 17 | Yes | Count enemy objects. It will search in all enemies. It uses `AND` comparator so each object must obey the same comparations | +| 18 | No | Count structures with `BridgeRepairHut=yes` linked with destroyed bridges | +| 19 | No | Count structures with `BridgeRepairHut=yes` linked with undamaged bridges | - Some trigger conditions need to specify a 0-based list from `[AITargetTypes]`. The index of the list is written in hexadecimal and in little endian format. The value must be written at the end of the trigger: `00000000000000000000000000000000000000000000000000000000AABBCCDD` diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 25245adaff..462e94fc32 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -379,14 +379,11 @@ void BuildingExt::ExtData::UpdateSecretLabAI() if (possibleCandidates.Count > 0) { - DynamicVectorClass ownedBuildingTypes; + std::map ownedBuildings; - if (ownedBuildingTypes.Count == 0) + for (auto building : pOwner->Buildings) { - for (auto building : pOwner->Buildings) - { - ownedBuildingTypes.AddUnique(building->Type); - } + ++ownedBuildings[building->Type]; } for (const auto& boon : possibleCandidates) @@ -405,7 +402,7 @@ void BuildingExt::ExtData::UpdateSecretLabAI() if (pOwner->Type->ArrayIndex == houseIdx) { - bool canBeBuilt = HouseExt::PrerequisitesMet(pOwner, boon, ownedBuildingTypes, true); + bool canBeBuilt = HouseExt::PrerequisitesMet(pOwner, boon, ownedBuildings, true); if (canBeBuilt) { diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 27d2dbde71..1c5dfb6f5b 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -74,7 +74,7 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa } } -bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes, bool skipSecretLabChecks) +bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, std::map ownedBuildings, bool skipSecretLabChecks) { if (!pThis || !pItem) return false; @@ -115,13 +115,17 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const { if (pTechno->Owner == pThis && pTechno->GetTechnoType() == pItem - && pTechno->IsAlive && pTechno->Health > 0) + && pTechno->IsAlive + && pTechno->Health > 0) { nInstances++; + + if (nInstances >= pItem->BuildLimit) + return false; } } - if (nInstances >= pItem->BuildLimit) + if (pItem->BuildLimit < 1) return false; bool prerequisiteNegativeMet = false; // Only one coincidence is needed @@ -134,18 +138,12 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (idx < 0) // Can be used generic prerequisites in this Ares tag? I have to investigate it but for now we support it... { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); } else { - for (auto pObject : ownedBuildingTypes) - { - if (prerequisiteNegativeMet) - break; - - if (idx == pObject->ArrayIndex) - prerequisiteNegativeMet = true; - } + if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + prerequisiteNegativeMet = true; } if (prerequisiteNegativeMet) @@ -172,18 +170,12 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (idx < 0) { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - prerequisiteOverrideMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + prerequisiteOverrideMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); } else { - for (auto pObject : ownedBuildingTypes) - { - if (prerequisiteOverrideMet) - break; - - if (idx == pObject->ArrayIndex) - prerequisiteOverrideMet = true; - } + if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + prerequisiteOverrideMet = true; } } } @@ -199,18 +191,12 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (idx < 0) { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - found = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); } else { - for (auto pObject : ownedBuildingTypes) - { - if (found) - break; - - if (idx == pObject->ArrayIndex) - found = true; - } + if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + found = true; } if (!found) @@ -242,20 +228,14 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const if (idx < 0) { // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - found = HouseExt::HasGenericPrerequisite(idx, ownedBuildingTypes); + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); } else { found = false; - for (auto pObject : ownedBuildingTypes) - { - if (idx == pObject->ArrayIndex) - found = true; - - if (found) - break; - } + if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + found = true; } if (!found) @@ -269,7 +249,8 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; } -bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClass ownedBuildingTypes) +bool HouseExt::HasGenericPrerequisite(int idx, std::map ownedBuildings) + { if (idx >= 0) return false; @@ -287,14 +268,8 @@ bool HouseExt::HasGenericPrerequisite(int idx, const DynamicVectorClassArrayIndex) - found = true; - } + if (ownedBuildings[TechnoTypeClass::Array->GetItem(idxItem)] > 0) + found = true; } return found; @@ -369,6 +344,7 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->Factory_NavyType) .Process(this->Factory_AircraftType) .Process(this->RepairBaseNodes) + .Process(this->AITriggers_ValidList) ; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 915a2d29bc..3aa3c51509 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -26,6 +26,8 @@ class HouseExt BuildingClass* Factory_NavyType; BuildingClass* Factory_AircraftType; + std::vector AITriggers_ValidList; + //Read from INI bool RepairBaseNodes[3]; @@ -78,7 +80,7 @@ class HouseExt static int ActiveHarvesterCount(HouseClass* pThis); static int TotalHarvesterCount(HouseClass* pThis); static HouseClass* GetHouseKind(OwnerHouseKind kind, bool allowRandom, HouseClass* pDefault, HouseClass* pInvoker = nullptr, HouseClass* pVictim = nullptr); - static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const DynamicVectorClass ownedBuildingTypes, bool skipSecretLabChecks); - static bool HasGenericPrerequisite(int idx, const DynamicVectorClass ownedBuildingTypes); + static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const std::map ownedBuildings, bool skipSecretLabChecks); + static bool HasGenericPrerequisite(int idx, std::map ownedBuildings); static int FindGenericPrerequisite(const char* id); }; diff --git a/src/Ext/Scenario/Hooks.cpp b/src/Ext/Scenario/Hooks.cpp new file mode 100644 index 0000000000..f2bfc4a021 --- /dev/null +++ b/src/Ext/Scenario/Hooks.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "Body.h" + +#include + +DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) +{ + // For each house save a list with only AI Triggers that can be used + for (HouseClass* pHouse : *HouseClass::Array) + { + auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + pHouseExt->AITriggers_ValidList.clear(); + int houseIdx = pHouse->ArrayIndex; + int sideIdx = pHouse->SideIndex + 1; + + for (int i = 0; i < AITriggerTypeClass::Array->Count; i++) + { + auto pTrigger = AITriggerTypeClass::Array->GetItem(i); + if (!pTrigger) + continue; + + int triggerHouse = pTrigger->HouseIndex; + int triggerSide = pTrigger->SideIndex; + + // The trigger must be compatible with the owner + if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + pHouseExt->AITriggers_ValidList.push_back(i); + } + + Debug::Log("House %d [%s] could use %d AI triggers in this map.\n", pHouse->ArrayIndex, pHouse->Type->ID, pHouseExt->AITriggers_ValidList.size()); + } + + return 0; +} diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index ec09d79692..0ce99d3969 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -242,7 +242,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { totalActiveTeams++; int teamHouseIdx = pRunningTeam->Owner->ArrayIndex; - //auto teamHouse = pRunningTeam->GetOwningHouse(); + if (teamHouseIdx != houseIdx) continue; @@ -254,11 +254,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) activeTeams = activeTeamsList.Count; - /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); + Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); for (auto team : activeTeamsList) { Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); - Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); + Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); int i = 0; for (auto entry : team->Type->TaskForce->Entries) @@ -272,7 +272,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) i++; } } - Debug::Log("=====================\n");*/ + Debug::Log("=====================\n"); // We will use these values for discarding triggers bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; @@ -287,38 +287,37 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) return SkipCode; } - // Obtain the real list of structures the house have - DynamicVectorClass ownedBuildingTypes; - for (auto building : pHouse->Buildings) - { - ownedBuildingTypes.AddUnique(building->Type); - } + int destroyedBridgesCount = 0; + int undamagedBridgesCount = 0; + std::map ownedRecruitables; + std::map ownedBuildings; - struct recruitableUnit + for (auto pTechno : *TechnoClass::Array) { - TechnoTypeClass* object = nullptr; - int count = 1; - - bool operator==(const TechnoTypeClass* other) const - { - return (object == other); - } - - bool operator==(const recruitableUnit other) const + if (pTechno->WhatAmI() == AbstractType::Building) { - return (object == other.object); - } - }; + if (pTechno->Owner == pHouse) + { + ++ownedBuildings[pTechno->GetTechnoType()]; + } + else + { + auto pBuilding = static_cast(pTechno); + if (pBuilding && pBuilding->Type->BridgeRepairHut) + { + CellStruct cell = pTechno->GetCell()->MapCoords; - // Build a list of recruitable units by the house - DynamicVectorClass recruitableUnits; + if (MapClass::Instance->IsLinkedBridgeDestroyed(cell)) + destroyedBridgesCount++; + else + undamagedBridgesCount++; + } + } - for (auto pTechno : *TechnoClass::Array) - { - if (pTechno->WhatAmI() == AbstractType::Building) continue; + } - FootClass* pFoot = static_cast(pTechno); + auto* pFoot = static_cast(pTechno); if (!pFoot || !pTechno->IsAlive @@ -331,25 +330,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; } - auto pTechnoType = pTechno->GetTechnoType(); - bool found = false; - - for (int i = 0; i < recruitableUnits.Count; i++) - { - if (recruitableUnits[i].object == pTechnoType) - { - recruitableUnits[i].count++; - found = true; - break; - } - } - - if (!found) - { - recruitableUnit newRecruitable; - newRecruitable.object = pTechnoType; - recruitableUnits.AddItem(newRecruitable); - } + ++ownedRecruitables[pTechno->GetTechnoType()]; } HouseClass* targetHouse = nullptr; @@ -382,8 +363,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - std::vector list; - list.push_back(pTrigger->ConditionObject); + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); if (!isConditionMet) @@ -395,8 +376,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - std::vector list; - list.push_back(pTrigger->ConditionObject); + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -408,8 +389,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - std::vector list; - list.push_back(pTrigger->ConditionObject); + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -421,8 +402,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - std::vector list; - list.push_back(pTrigger->ConditionObject); + DynamicVectorClass list; + list.AddItem(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -430,11 +411,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 9) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); if (!isConditionMet) @@ -443,11 +424,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 10) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -456,11 +437,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 11) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -469,11 +450,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 12) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -482,11 +463,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 13) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); if (!isConditionMet) @@ -495,11 +476,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 14) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 14: Like in case 9 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); if (!isConditionMet) @@ -508,11 +489,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 15) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 15: Like in case 10 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); if (!isConditionMet) @@ -521,11 +502,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 16) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 16: Like in case 11 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); if (!isConditionMet) @@ -534,17 +515,33 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 17) { - if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) { // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); if (!isConditionMet) continue; } } + else if ((int)pTrigger->ConditionType == 18) + { + // New case 18: Check destroyed bridges + bool isConditionMet = TeamExt::CountConditionMet(pTrigger, destroyedBridgesCount); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 19) + { + // New case 19: Check undamaged bridges + bool isConditionMet = TeamExt::CountConditionMet(pTrigger, undamagedBridgesCount); + + if (!isConditionMet) + continue; + } else { // Other cases from vanilla game @@ -666,7 +663,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; TechnoTypeClass* object = entry.Type; - bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildingTypes, false); + bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildings, false); if (!canBeBuilt) { @@ -694,20 +691,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) // Check if each unit in the taskforce has the available recruitable units in the map if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) { - bool canBeRecruited = false; - - for (auto item : recruitableUnits) - { - if (item.object == entry.Type) - { - if (item.count >= entry.Amount) - canBeRecruited = true; - - break; - } - } - - if (!canBeRecruited) + if (ownedRecruitables[entry.Type] < entry.Amount) { allObjectsCanBeBuiltOrRecruited = false; break; @@ -986,7 +970,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!selectedTrigger) { - Debug::Log("DEBUG: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AI Team Selector: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } @@ -998,7 +982,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } // We have a winner trigger here - Debug::Log("DEBUG: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); + Debug::Log("AI Team Selector: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); // Team 1 creation auto pTriggerTeam1Type = selectedTrigger->Team1; @@ -1014,8 +998,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (count < pTriggerTeam1Type->Max) { - if (auto newTeam = pTriggerTeam1Type->CreateTeam(pHouse)) - newTeam->NeedsToDisappear = false; + if (TeamClass* newTeam1 = pTriggerTeam1Type->CreateTeam(pHouse)) + newTeam1->NeedsToDisappear = false; } } @@ -1033,8 +1017,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (count < pTriggerTeam2Type->Max) { - if (auto newTeam = pTriggerTeam2Type->CreateTeam(pHouse)) - newTeam->NeedsToDisappear = false; + if (TeamClass* newTeam2 = pTriggerTeam2Type->CreateTeam(pHouse)) + newTeam2->NeedsToDisappear = false; } } } @@ -1042,7 +1026,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) return SkipCode; } -bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list) +bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list) { bool result = false; int counter = 0; @@ -1091,7 +1075,7 @@ bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool alli return result; } -bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list) { bool result = false; int counter = 0; @@ -1144,7 +1128,7 @@ bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClas return result; } -bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) +bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) { bool result = false; int counter = 0; @@ -1198,11 +1182,11 @@ bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) +bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list) { bool result = true; - if (list.size() == 0) + if (list.Count == 0) return false; // Count all objects of the list, like an AND operator @@ -1254,14 +1238,14 @@ bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::v return result; } -bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list) +bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list) { bool result = true; if (pEnemy && pHouse->IsAlliedWith(pEnemy)) pEnemy = nullptr; - if (list.size() == 0) + if (list.Count == 0) return false; // Count all objects of the list, like an AND operator @@ -1315,11 +1299,11 @@ bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseC return result; } -bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list) +bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list) { bool result = true; - if (list.size() == 0) + if (list.Count == 0) return false; // Any neutral house should be capable to meet the prerequisites @@ -1384,3 +1368,37 @@ bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vectorConditions->ComparatorOperand) + { + case 0: + result = nObjects < pThis->Conditions->ComparatorType; + break; + case 1: + result = nObjects <= pThis->Conditions->ComparatorType; + break; + case 2: + result = nObjects == pThis->Conditions->ComparatorType; + break; + case 3: + result = nObjects >= pThis->Conditions->ComparatorType; + break; + case 4: + result = nObjects > pThis->Conditions->ComparatorType; + break; + case 5: + result = nObjects != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index f6d06a12ff..c35506eb6b 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -144,11 +144,12 @@ class TeamExt static ExtContainer ExtMap; - static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list); - static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list); - static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list); - static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list); - static bool NeutralOwns(AITriggerTypeClass* pThis, std::vector list); - static bool NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list); + static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list); + static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list); + static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list); + static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list); + static bool NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list); + static bool NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list); + static bool CountConditionMet(AITriggerTypeClass* pThis, int nObjects); }; From 28e715b8fd212a0cbf9c7422d0d2c0ec6eb00a40 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 26 May 2023 23:51:58 +0200 Subject: [PATCH 17/34] fixed the object type I changed by mistake --- src/Ext/Team/Body.cpp | 70 +++++++++++++++++++++---------------------- src/Ext/Team/Body.h | 12 ++++---- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 0ce99d3969..1d12fa9e25 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -363,8 +363,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); if (!isConditionMet) @@ -376,8 +376,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -389,8 +389,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -402,8 +402,8 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger->ConditionObject) continue; - DynamicVectorClass list; - list.AddItem(pTrigger->ConditionObject); + std::vector list; + list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -411,11 +411,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 9) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); if (!isConditionMet) @@ -424,11 +424,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 10) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); if (!isConditionMet) @@ -437,11 +437,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 11) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); if (!isConditionMet) @@ -450,11 +450,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 12) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); if (!isConditionMet) @@ -463,11 +463,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 13) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); if (!isConditionMet) @@ -476,11 +476,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 14) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 14: Like in case 9 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); if (!isConditionMet) @@ -489,11 +489,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 15) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 15: Like in case 10 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); if (!isConditionMet) @@ -502,11 +502,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 16) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 16: Like in case 11 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); if (!isConditionMet) @@ -515,11 +515,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 17) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.Count) + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) { // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - DynamicVectorClass list = RulesExt::Global()->AITargetTypesLists.GetItem(pTrigger->Conditions[3].ComparatorOperand); + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); if (!isConditionMet) @@ -1026,7 +1026,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) return SkipCode; } -bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list) +bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list) { bool result = false; int counter = 0; @@ -1075,7 +1075,7 @@ bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool alli return result; } -bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list) +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) { bool result = false; int counter = 0; @@ -1128,7 +1128,7 @@ bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClas return result; } -bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) { bool result = false; int counter = 0; @@ -1182,11 +1182,11 @@ bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list) { bool result = true; - if (list.Count == 0) + if (list.size() == 0) return false; // Count all objects of the list, like an AND operator @@ -1238,14 +1238,14 @@ bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, Dynami return result; } -bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list) +bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list) { bool result = true; if (pEnemy && pHouse->IsAlliedWith(pEnemy)) pEnemy = nullptr; - if (list.Count == 0) + if (list.size() == 0) return false; // Count all objects of the list, like an AND operator @@ -1299,11 +1299,11 @@ bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseC return result; } -bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list) +bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list) { bool result = true; - if (list.Count == 0) + if (list.size() == 0) return false; // Any neutral house should be capable to meet the prerequisites diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index c35506eb6b..be1bd9f578 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -144,12 +144,12 @@ class TeamExt static ExtContainer ExtMap; - static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, DynamicVectorClass list); - static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, DynamicVectorClass list); - static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, DynamicVectorClass list); - static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, DynamicVectorClass list); - static bool NeutralOwns(AITriggerTypeClass* pThis, DynamicVectorClass list); - static bool NeutralOwnsAll(AITriggerTypeClass* pThis, DynamicVectorClass list); + static bool HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list); + static bool HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list); + static bool EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list); + static bool EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list); + static bool NeutralOwns(AITriggerTypeClass* pThis, std::vector list); + static bool NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list); static bool CountConditionMet(AITriggerTypeClass* pThis, int nObjects); }; From 809e022be4d9f7c980d585cb06f1b2412dabdf66 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 26 May 2023 23:55:24 +0200 Subject: [PATCH 18/34] Comment a debug block --- src/Ext/Team/Body.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 1d12fa9e25..ec3ffae80c 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -254,7 +254,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) activeTeams = activeTeamsList.Count; - Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); + /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); for (auto team : activeTeamsList) { Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); @@ -272,7 +272,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) i++; } } - Debug::Log("=====================\n"); + Debug::Log("=====================\n");*/ // We will use these values for discarding triggers bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; From 7e1796080100630e5b610ddc756998f8d8ae6441 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 27 May 2023 00:15:57 +0200 Subject: [PATCH 19/34] Point to latest YRpp --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index 5ad1070ce1..bf7425e1d2 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 5ad1070ce1446eb69d205703159cd70f9f580952 +Subproject commit bf7425e1d2cf2353e53919362ab9e4c0c664e9b1 From 2687a956863ddebd7a37e81bf98e7754b715870c Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 13 Dec 2023 17:40:45 +0100 Subject: [PATCH 20/34] Clear some vectors before filling with elements Fix a small bug in custom maps --- src/Ext/TechnoType/Body.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 1db6f95a35..d5f14c59c3 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -411,6 +411,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ConsideredSecretLabTech.Read(exINI, pSection, "ConsideredSecretLabTech"); // Secret.RequiredHouses contains a list of HouseTypeClass indexes + this->Secret_RequiredHouses.clear(); const char* key = "SecretLab.RequiredHouses"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -424,6 +425,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Secret.ForbiddenHouses contains a list of HouseTypeClass indexes + this->Secret_ForbiddenHouses.clear(); key = "SecretLab.ForbiddenHouses"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -437,6 +439,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite.RequiredTheaters contains a list of theader names + this->Prerequisite_RequiredTheaters.clear(); key = "Prerequisite.RequiredTheaters"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -452,6 +455,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) // Prerequisite with Generic Prerequistes support. // Note: I have no idea of what could happen in all the game engine logics if I push the negative indexes of the Ares generic prerequisites directly into the original Prerequisite tag... for that reason this tag is duplicated for working with it + this->Prerequisite.clear(); key = "Prerequisite"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -474,6 +478,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite.Negative with Generic Prerequistes support + this->Prerequisite_Negative.clear(); key = "Prerequisite.Negative"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -496,6 +501,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite.ListX with Generic Prerequistes support + this->Prerequisite_ListVector.clear(); this->Prerequisite_Lists.Read(exINI, pSection, "Prerequisite.Lists"); if (Prerequisite_Lists.Get() > 0) @@ -524,7 +530,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) } } - Prerequisite_ListVector.push_back(objectsList); + this->Prerequisite_ListVector.push_back(objectsList); objectsList.Clear(); } } From 8477602a85457946f60c17d6007eae58ca949e55 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 22 Dec 2023 09:37:44 +0100 Subject: [PATCH 21/34] ParentCountry support Necessary for Single player missions --- src/Ext/Team/Body.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index e12445571e..06ea5203fd 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -227,8 +227,14 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } } + int parentCountryTypeIdx = pHouse->Type->FindParentCountryIndex(); // ParentCountry can change the House in a SP map + int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based int houseIdx = pHouse->ArrayIndex; - int sideIdx = pHouse->SideIndex + 1; + + int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; + int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based + int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based + auto houseDifficulty = pHouse->AIDifficulty; int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); int activeDefenseTeamsCount = 0; @@ -356,7 +362,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->IsEnabled) { // The trigger must be compatible with the owner - if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + if ((triggerHouse == -1 || houseTypeIdx == triggerHouse) && (triggerSide == 0 || sideTypeIdx == triggerSide)) { // "ConditionType=-1" will be skipped, always is valid if ((int)pTrigger->ConditionType >= 0) From 0a8284fa24a7d75dd5a9278ade00c39650ec16e4 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 11 Mar 2024 19:38:43 +0100 Subject: [PATCH 22/34] Fix compilation --- src/Ext/HouseType/Body.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ext/HouseType/Body.cpp b/src/Ext/HouseType/Body.cpp index 9dfeccf13b..9f990dbc92 100644 --- a/src/Ext/HouseType/Body.cpp +++ b/src/Ext/HouseType/Body.cpp @@ -2,7 +2,7 @@ #include -template<> const DWORD Extension::Canary = 0x1111111A; +static constexpr DWORD Canary = 0x1111111A; HouseTypeExt::ExtContainer HouseTypeExt::ExtMap; void HouseTypeExt::ExtData::Initialize() @@ -89,7 +89,7 @@ DEFINE_HOOK(0x511635, HouseTypeClass_CTOR_1, 0x5) { GET(HouseTypeClass*, pItem, EAX); - HouseTypeExt::ExtMap.FindOrAllocate(pItem); + HouseTypeExt::ExtMap.Allocate(pItem); return 0; } @@ -98,7 +98,7 @@ DEFINE_HOOK(0x511643, HouseTypeClass_CTOR_2, 0x5) { GET(HouseTypeClass*, pItem, EAX); - HouseTypeExt::ExtMap.FindOrAllocate(pItem); + HouseTypeExt::ExtMap.Allocate(pItem); return 0; } From 5a2fa0f0bc3bb88481ca13531125db51865d2484 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 29 Apr 2024 07:12:02 +0200 Subject: [PATCH 23/34] Some prerequisites Fixes --- src/Ext/Building/Body.cpp | 2 +- src/Ext/House/Body.cpp | 18 ++++++++++-------- src/Ext/House/Body.h | 4 ++-- src/Ext/Rules/Body.cpp | 19 +++++++++++++++++++ src/Ext/Team/Body.cpp | 6 +++--- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 19e2427633..043715f36e 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -380,7 +380,7 @@ void BuildingExt::ExtData::UpdateSecretLabAI() if (possibleCandidates.Count > 0) { - std::map ownedBuildings; + std::map ownedBuildings; for (auto building : pOwner->Buildings) { diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index a5bb9cd139..09cee3da73 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -380,7 +380,7 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa } } -bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, std::map ownedBuildings, bool skipSecretLabChecks) +bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, std::map ownedBuildings, bool skipSecretLabChecks) { if (!pThis || !pItem) return false; @@ -448,7 +448,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } else { - if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) prerequisiteNegativeMet = true; } @@ -480,7 +480,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } else { - if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) prerequisiteOverrideMet = true; } } @@ -501,7 +501,8 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const } else { - if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + auto debugType = BuildingTypeClass::Array->GetItem(idx); + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) found = true; } @@ -540,7 +541,7 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const { found = false; - if (ownedBuildings[TechnoTypeClass::Array->GetItem(idx)] > 0) + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) found = true; } @@ -555,14 +556,14 @@ bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; } -bool HouseExt::HasGenericPrerequisite(int idx, std::map ownedBuildings) +bool HouseExt::HasGenericPrerequisite(int idx, std::map ownedBuildings) { if (idx >= 0) return false; DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); - //auto selectedPrerequisiteName = RulesExt::Global()->GenericPrerequisitesNames.GetItem(std::abs(idx));// Only used for easy debug + const char* selectedPrerequisiteName = RulesExt::Global()->GenericPrerequisitesNames[std::abs(idx)];// Only used for easy debug if (selectedPrerequisite.Count == 0) return false; @@ -574,7 +575,8 @@ bool HouseExt::HasGenericPrerequisite(int idx, std::map o if (found) break; - if (ownedBuildings[TechnoTypeClass::Array->GetItem(idxItem)] > 0) + auto debugType = BuildingTypeClass::Array->GetItem(idxItem); + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idxItem)) > 0) found = true; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 0317ac6857..ebcb2e3600 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -115,8 +115,8 @@ class HouseExt static int TotalHarvesterCount(HouseClass* pThis); static HouseClass* GetHouseKind(OwnerHouseKind kind, bool allowRandom, HouseClass* pDefault, HouseClass* pInvoker = nullptr, HouseClass* pVictim = nullptr); static CellClass* GetEnemyBaseGatherCell(HouseClass* pTargetHouse, HouseClass* pCurrentHouse, CoordStruct defaultCurrentCoords, SpeedType speedTypeZone, int extraDistance = 0); - static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const std::map ownedBuildings, bool skipSecretLabChecks); - static bool HasGenericPrerequisite(int idx, std::map ownedBuildings); + static bool PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, const std::map ownedBuildings, bool skipSecretLabChecks); + static bool HasGenericPrerequisite(int idx, std::map ownedBuildings); static int FindGenericPrerequisite(const char* id); static void SetSkirmishHouseName(HouseClass* pHouse); diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 4da6eaa086..06ac0830bd 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -281,6 +281,25 @@ void RulesExt::FillDefaultPrerequisites() objectsList.Clear(); } + + // Debug + /*if (RulesExt::Global()->GenericPrerequisites.Count > 0) + { + Debug::Log("[GenericPrerequisites] = %d\n", RulesExt::Global()->GenericPrerequisites.Count); + Debug::Log("[GenericPrerequisites] (ID names) = %d\n\n", RulesExt::Global()->GenericPrerequisitesNames.Count); + + for (int i = 0; i < RulesExt::Global()->GenericPrerequisites.Count; i++) + { + auto list = RulesExt::Global()->GenericPrerequisites.GetItem(i); + const auto listName = RulesExt::Global()->GenericPrerequisitesNames[i]; + + for (int j : list) + { + auto pTypeName = BuildingTypeClass::Array->GetItem(j)->ID; + Debug::Log("[GenericPrerequisites][%s](%d) -> [%s](%d)\n", listName, i, pTypeName, j); + } + } + }*/ } // ============================= diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index 06ea5203fd..b809226c31 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -300,7 +300,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int destroyedBridgesCount = 0; int undamagedBridgesCount = 0; std::map ownedRecruitables; - std::map ownedBuildings; + std::map ownedBuildings; for (auto pTechno : *TechnoClass::Array) { @@ -308,7 +308,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { if (pTechno->Owner == pHouse) { - ++ownedBuildings[pTechno->GetTechnoType()]; + ++ownedBuildings[static_cast(pTechno->GetTechnoType())]; } else { @@ -701,7 +701,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) // Check if each unit in the taskforce has the available recruitable units in the map if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) { - if (ownedRecruitables[entry.Type] < entry.Amount) + if (ownedRecruitables.count(entry.Type) < entry.Amount) { allObjectsCanBeBuiltOrRecruited = false; break; From 149a718643fb32bbb1337b5012d3a4cfeaa21da3 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 29 Apr 2024 08:57:11 +0200 Subject: [PATCH 24/34] Moved code into its own file --- Phobos.vcxproj | 2 + src/Ext/House/Body.Prerequisites.cpp | 224 ++++ src/Ext/House/Body.cpp | 223 ---- src/Ext/Team/Body.NewTeamsSelector.cpp | 1320 ++++++++++++++++++++++++ src/Ext/Team/Body.cpp | 1319 ----------------------- 5 files changed, 1546 insertions(+), 1542 deletions(-) create mode 100644 src/Ext/House/Body.Prerequisites.cpp create mode 100644 src/Ext/Team/Body.NewTeamsSelector.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 6210e9bd25..cf9aedf0cc 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -38,6 +38,7 @@ + @@ -53,6 +54,7 @@ + diff --git a/src/Ext/House/Body.Prerequisites.cpp b/src/Ext/House/Body.Prerequisites.cpp new file mode 100644 index 0000000000..7d190fe450 --- /dev/null +++ b/src/Ext/House/Body.Prerequisites.cpp @@ -0,0 +1,224 @@ +#include "body.h" + +bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, std::map ownedBuildings, bool skipSecretLabChecks) +{ + if (!pThis || !pItem) + return false; + + auto pHouseExt = HouseExt::ExtMap.Find(pThis); + if (!pHouseExt) + return false; + + auto pItemExt = TechnoTypeExt::ExtMap.Find(pItem); + if (!pItemExt) + return false; + + // If the unit is available after capturing a SecretLab=yes must be evaluated if meets the prerequisite + if (!skipSecretLabChecks && pItemExt->ConsideredSecretLabTech && !pThis->HasFromSecretLab(pItem)) + return false; + + // Check if it appears in Owner=, RequiredHouses= and ForbiddenHouses= + // Note: if RequiredHouses = tag doesn't exist InRequiredHouses() always returns TRUE + if (!pThis->InOwners(pItem) || !pThis->InRequiredHouses(pItem) || pThis->InForbiddenHouses(pItem)) + return false; + + // Prerequisite.RequiredTheaters check + if (pItemExt->Prerequisite_RequiredTheaters.size() > 0) + { + int currentTheaterIndex = (int)ScenarioClass::Instance->Theater; + if (pItemExt->Prerequisite_RequiredTheaters.IndexOf(currentTheaterIndex) < 0) + return false; + } + + // TechLevel check + if (pThis->TechLevel < pItem->TechLevel) + return false; + + // BuildLimit checks + int nInstances = 0; + + for (auto pTechno : *TechnoClass::Array) + { + if (pTechno->Owner == pThis + && pTechno->GetTechnoType() == pItem + && pTechno->IsAlive + && pTechno->Health > 0) + { + nInstances++; + + if (nInstances >= pItem->BuildLimit) + return false; + } + } + + if (pItem->BuildLimit < 1) + return false; + + bool prerequisiteNegativeMet = false; // Only one coincidence is needed + + // Ares Prerequisite.Negative list + if (pItemExt->Prerequisite_Negative.size() > 0) + { + for (int idx : pItemExt->Prerequisite_Negative) + { + if (idx < 0) // Can be used generic prerequisites in this Ares tag? I have to investigate it but for now we support it... + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); + } + else + { + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) + prerequisiteNegativeMet = true; + } + + if (prerequisiteNegativeMet) + return false; + } + } + + // Main prerequisite checks are skipped if a new secret lab object is in process to be unlocked + if (skipSecretLabChecks) + return true; + + DynamicVectorClass prerequisiteOverride = pItem->PrerequisiteOverride; + + bool prerequisiteMet = false; // All buildings must appear in the buildings list owner by the house + bool prerequisiteOverrideMet = false; // This tag uses an OR comparator: Only one coincidence is needed + + if (prerequisiteOverride.Count > 0) + { + for (int idx : prerequisiteOverride) + { + if (prerequisiteOverrideMet) + break; + + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + prerequisiteOverrideMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); + } + else + { + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) + prerequisiteOverrideMet = true; + } + } + } + + if (pItemExt->Prerequisite.size() > 0) + { + bool found = false; + + for (int idx : pItemExt->Prerequisite) + { + found = false; + + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); + } + else + { + auto debugType = BuildingTypeClass::Array->GetItem(idx); + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) + found = true; + } + + if (!found) + break; + } + + prerequisiteMet = found; + } + else + { + // No prerequisites list means that always is buildable + prerequisiteMet = true; + } + + bool prerequisiteListsMet = false; + + // Ares Prerequisite lists + if (pItemExt->Prerequisite_Lists.Get() > 0) + { + bool found = false; + + for (auto list : pItemExt->Prerequisite_ListVector) + { + if (found) + break; + + for (int idx : list) + { + if (idx < 0) + { + // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... + found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); + } + else + { + found = false; + + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) + found = true; + } + + if (!found) + break; + } + } + + prerequisiteListsMet = found; + } + + return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; +} + +bool HouseExt::HasGenericPrerequisite(int idx, std::map ownedBuildings) + +{ + if (idx >= 0) + return false; + + DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); + const char* selectedPrerequisiteName = RulesExt::Global()->GenericPrerequisitesNames[std::abs(idx)];// Only used for easy debug + + if (selectedPrerequisite.Count == 0) + return false; + + bool found = false; + + for (auto idxItem : selectedPrerequisite) + { + if (found) + break; + + auto debugType = BuildingTypeClass::Array->GetItem(idxItem); + if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idxItem)) > 0) + found = true; + } + + return found; +} + +int HouseExt::FindGenericPrerequisite(const char* id) +{ + if (TechnoTypeClass::FindIndex(id) >= 0) + return 0; + + if (RulesExt::Global()->GenericPrerequisitesNames.Count == 0) + RulesExt::FillDefaultPrerequisites(); // needed! + + int i = 0; + for (auto str : RulesExt::Global()->GenericPrerequisitesNames) + { + if (_strcmpi(id, str) == 0) + return (-1 * i); + + ++i; + } + + return 0; +} diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 09cee3da73..1422813550 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -380,229 +380,6 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa } } -bool HouseExt::PrerequisitesMet(HouseClass* const pThis, TechnoTypeClass* const pItem, std::map ownedBuildings, bool skipSecretLabChecks) -{ - if (!pThis || !pItem) - return false; - - auto pHouseExt = HouseExt::ExtMap.Find(pThis); - if (!pHouseExt) - return false; - - auto pItemExt = TechnoTypeExt::ExtMap.Find(pItem); - if (!pItemExt) - return false; - - // If the unit is available after capturing a SecretLab=yes must be evaluated if meets the prerequisite - if (!skipSecretLabChecks && pItemExt->ConsideredSecretLabTech && !pThis->HasFromSecretLab(pItem)) - return false; - - // Check if it appears in Owner=, RequiredHouses= and ForbiddenHouses= - // Note: if RequiredHouses = tag doesn't exist InRequiredHouses() always returns TRUE - if (!pThis->InOwners(pItem) || !pThis->InRequiredHouses(pItem) || pThis->InForbiddenHouses(pItem)) - return false; - - // Prerequisite.RequiredTheaters check - if (pItemExt->Prerequisite_RequiredTheaters.size() > 0) - { - int currentTheaterIndex = (int)ScenarioClass::Instance->Theater; - if (pItemExt->Prerequisite_RequiredTheaters.IndexOf(currentTheaterIndex) < 0) - return false; - } - - // TechLevel check - if (pThis->TechLevel < pItem->TechLevel) - return false; - - // BuildLimit checks - int nInstances = 0; - - for (auto pTechno : *TechnoClass::Array) - { - if (pTechno->Owner == pThis - && pTechno->GetTechnoType() == pItem - && pTechno->IsAlive - && pTechno->Health > 0) - { - nInstances++; - - if (nInstances >= pItem->BuildLimit) - return false; - } - } - - if (pItem->BuildLimit < 1) - return false; - - bool prerequisiteNegativeMet = false; // Only one coincidence is needed - - // Ares Prerequisite.Negative list - if (pItemExt->Prerequisite_Negative.size() > 0) - { - for (int idx : pItemExt->Prerequisite_Negative) - { - if (idx < 0) // Can be used generic prerequisites in this Ares tag? I have to investigate it but for now we support it... - { - // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - prerequisiteNegativeMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); - } - else - { - if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) - prerequisiteNegativeMet = true; - } - - if (prerequisiteNegativeMet) - return false; - } - } - - // Main prerequisite checks are skipped if a new secret lab object is in process to be unlocked - if (skipSecretLabChecks) - return true; - - DynamicVectorClass prerequisiteOverride = pItem->PrerequisiteOverride; - - bool prerequisiteMet = false; // All buildings must appear in the buildings list owner by the house - bool prerequisiteOverrideMet = false; // This tag uses an OR comparator: Only one coincidence is needed - - if (prerequisiteOverride.Count > 0) - { - for (int idx : prerequisiteOverride) - { - if (prerequisiteOverrideMet) - break; - - if (idx < 0) - { - // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - prerequisiteOverrideMet = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); - } - else - { - if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) - prerequisiteOverrideMet = true; - } - } - } - - if (pItemExt->Prerequisite.size() > 0) - { - bool found = false; - - for (int idx : pItemExt->Prerequisite) - { - found = false; - - if (idx < 0) - { - // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); - } - else - { - auto debugType = BuildingTypeClass::Array->GetItem(idx); - if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) - found = true; - } - - if (!found) - break; - } - - prerequisiteMet = found; - } - else - { - // No prerequisites list means that always is buildable - prerequisiteMet = true; - } - - bool prerequisiteListsMet = false; - - // Ares Prerequisite lists - if (pItemExt->Prerequisite_Lists.Get() > 0) - { - bool found = false; - - for (auto list : pItemExt->Prerequisite_ListVector) - { - if (found) - break; - - for (int idx : list) - { - if (idx < 0) - { - // Default prerequisites like POWER, PROC, BARRACKS, FACTORY, ... - found = HouseExt::HasGenericPrerequisite(idx, ownedBuildings); - } - else - { - found = false; - - if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idx)) > 0) - found = true; - } - - if (!found) - break; - } - } - - prerequisiteListsMet = found; - } - - return prerequisiteMet || prerequisiteListsMet || prerequisiteOverrideMet; -} - -bool HouseExt::HasGenericPrerequisite(int idx, std::map ownedBuildings) - -{ - if (idx >= 0) - return false; - - DynamicVectorClass selectedPrerequisite = RulesExt::Global()->GenericPrerequisites.GetItem(std::abs(idx)); - const char* selectedPrerequisiteName = RulesExt::Global()->GenericPrerequisitesNames[std::abs(idx)];// Only used for easy debug - - if (selectedPrerequisite.Count == 0) - return false; - - bool found = false; - - for (auto idxItem : selectedPrerequisite) - { - if (found) - break; - - auto debugType = BuildingTypeClass::Array->GetItem(idxItem); - if (ownedBuildings.count(BuildingTypeClass::Array->GetItem(idxItem)) > 0) - found = true; - } - - return found; -} - -int HouseExt::FindGenericPrerequisite(const char* id) -{ - if (TechnoTypeClass::FindIndex(id) >= 0) - return 0; - - if (RulesExt::Global()->GenericPrerequisitesNames.Count == 0) - RulesExt::FillDefaultPrerequisites(); // needed! - - int i = 0; - for (auto str : RulesExt::Global()->GenericPrerequisitesNames) - { - if (_strcmpi(id, str) == 0) - return (-1 * i); - - ++i; - } - - return 0; -} - void HouseExt::ExtData::UpdateAutoDeathObjectsInLimbo() { for (auto const pExt : this->OwnedAutoDeathObjects) diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp new file mode 100644 index 0000000000..938bd2fbed --- /dev/null +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -0,0 +1,1320 @@ +#include "Body.h" + +DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) +{ + enum { UseOriginalSelector = 0x4F8A63, SkipCode = 0x4F8B08 }; + + GET(HouseClass*, pHouse, ESI); + + bool houseIsHuman = pHouse->IsHumanPlayer; + + if (SessionClass::IsCampaign()) + houseIsHuman = pHouse->IsHumanPlayer || pHouse->IsInPlayerControl; + + if (houseIsHuman || pHouse->Type->MultiplayPassive) + return SkipCode; + + auto pHouseTypeExt = HouseTypeExt::ExtMap.Find(pHouse->Type); + if (!pHouseTypeExt) + return SkipCode; + + if (!RulesExt::Global()->NewTeamsSelector) + return UseOriginalSelector; + + // Reset Team selection countdown + int countdown = RulesClass::Instance->TeamDelays[(int)pHouse->AIDifficulty]; + pHouse->TeamDelayTimer.Start(countdown); + + int totalActiveTeams = 0; + int activeTeams = 0; + + int totalGroundCategoryTriggers = 0; + int totalUnclassifiedCategoryTriggers = 0; + int totalNavalCategoryTriggers = 0; + int totalAirCategoryTriggers = 0; + + DynamicVectorClass validTriggerCandidates; + DynamicVectorClass validTriggerCandidatesGroundOnly; + DynamicVectorClass validTriggerCandidatesNavalOnly; + DynamicVectorClass validTriggerCandidatesAirOnly; + DynamicVectorClass validTriggerCandidatesUnclassifiedOnly; + + int dice = ScenarioClass::Instance->Random.RandomRanged(1, 100); + + // This house must have the triggers enabled + if (dice <= pHouse->RatioAITriggerTeam && pHouse->AITriggersActive) + { + bool splitTriggersByCategory = RulesExt::Global()->NewTeamsSelector_SplitTriggersByCategory; + bool isFallbackEnabled = RulesExt::Global()->NewTeamsSelector_EnableFallback; + teamCategory validCategory = teamCategory::None; + int mergeUnclassifiedCategoryWith = -1; + + double percentageUnclassifiedTriggers = 0.0; + double percentageGroundTriggers = 0.0; + double percentageNavalTriggers = 0.0; + double percentageAirTriggers = 0.0; + + if (splitTriggersByCategory) + { + mergeUnclassifiedCategoryWith = pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.isset() ? pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.Get() : RulesExt::Global()->NewTeamsSelector_MergeUnclassifiedCategoryWith; // Should mixed teams be merged into another category? + percentageUnclassifiedTriggers = pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_UnclassifiedCategoryPercentage; // Mixed teams + percentageGroundTriggers = pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_GroundCategoryPercentage; // Only ground + percentageNavalTriggers = pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_NavalCategoryPercentage; // Only Naval=yes + percentageAirTriggers = pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_AirCategoryPercentage; // Only Aircrafts & jumpjets + + // Merge mixed category with another category, if set + if (mergeUnclassifiedCategoryWith >= 0) + { + switch (mergeUnclassifiedCategoryWith) + { + case (int)teamCategory::Ground: + percentageGroundTriggers += percentageUnclassifiedTriggers; + break; + + case (int)teamCategory::Air: + percentageAirTriggers += percentageUnclassifiedTriggers; + break; + + case (int)teamCategory::Naval: + percentageNavalTriggers += percentageUnclassifiedTriggers; + break; + + default: + break; + } + + percentageUnclassifiedTriggers = 0.0; + } + + percentageUnclassifiedTriggers = percentageUnclassifiedTriggers < 0.0 || percentageUnclassifiedTriggers > 1.0 ? 0.0 : percentageUnclassifiedTriggers; + percentageGroundTriggers = percentageGroundTriggers < 0.0 || percentageGroundTriggers > 1.0 ? 0.0 : percentageGroundTriggers; + percentageNavalTriggers = percentageNavalTriggers < 0.0 || percentageNavalTriggers > 1.0 ? 0.0 : percentageNavalTriggers; + percentageAirTriggers = percentageAirTriggers < 0.0 || percentageAirTriggers > 1.0 ? 0.0 : percentageAirTriggers; + + double totalPercengates = percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers; + if (totalPercengates > 1.0 || totalPercengates <= 0.0) + splitTriggersByCategory = false; + + + if (splitTriggersByCategory) + { + int categoryDice = ScenarioClass::Instance->Random.RandomRanged(1, 100); + int unclassifiedValue = (int)(percentageUnclassifiedTriggers * 100.0); + int groundValue = (int)(percentageGroundTriggers * 100.0); + int airValue = (int)(percentageAirTriggers * 100.0); + int navalValue = (int)(percentageNavalTriggers * 100.0); + + // Pick what type of team will be selected in this round + if (percentageUnclassifiedTriggers > 0.0 && categoryDice <= unclassifiedValue) + { + validCategory = teamCategory::Unclassified; + Debug::Log("New AI team category selection: dice %d <= %d (MIXED)\n", categoryDice, unclassifiedValue); + } + else if (percentageGroundTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue)) + { + validCategory = teamCategory::Ground; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + GROUND: %d%%)\n", categoryDice, (unclassifiedValue + groundValue), unclassifiedValue, groundValue); + } + else if (percentageAirTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue)) + { + validCategory = teamCategory::Air; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + AIR: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue), unclassifiedValue, groundValue, airValue); + } + else if (percentageNavalTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue + navalValue)) + { + validCategory = teamCategory::Naval; + Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + air: %d%% + NAVAL: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue + navalValue), unclassifiedValue, groundValue, airValue, navalValue); + } + else + { + // If the sum of all percentages is less than 100% then that empty space will work like "no categories" + splitTriggersByCategory = false; + } + } + } + + int parentCountryTypeIdx = pHouse->Type->FindParentCountryIndex(); // ParentCountry can change the House in a SP map + int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based + int houseIdx = pHouse->ArrayIndex; + + int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; + int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based + int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based + + auto houseDifficulty = pHouse->AIDifficulty; + int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); + int activeDefenseTeamsCount = 0; + int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); + double totalWeight = 0.0; + double totalWeightGroundOnly = 0.0; + double totalWeightNavalOnly = 0.0; + double totalWeightAirOnly = 0.0; + double totalWeightUnclassifiedOnly = 0.0; + + // Check if the running teams by the house already reached all the limits + DynamicVectorClass activeTeamsList; + + for (auto const pRunningTeam : *TeamClass::Array) + { + totalActiveTeams++; + int teamHouseIdx = pRunningTeam->Owner->ArrayIndex; + + if (teamHouseIdx != houseIdx) + continue; + + activeTeamsList.AddItem(pRunningTeam); + + if (pRunningTeam->Type->IsBaseDefense && activeDefenseTeamsCount < maxBaseDefenseTeams) + activeDefenseTeamsCount++; + } + + activeTeams = activeTeamsList.Count; + + /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); + for (auto team : activeTeamsList) + { + Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); + Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); + int i = 0; + + for (auto entry : team->Type->TaskForce->Entries) + { + if (entry.Type && entry.Amount > 0) + { + if (entry.Type) + Debug::Log("\t[%s]: %d / %d\n", entry.Type->ID, team->CountObjects[i], entry.Amount); + } + + i++; + } + } + Debug::Log("=====================\n");*/ + + // We will use these values for discarding triggers + bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; + bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; + + if (hasReachedMaxDefensiveTeamsLimit) + Debug::Log("DEBUG: House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + + if (hasReachedMaxTeamsLimit) + { + Debug::Log("DEBUG: House [%s] (idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + int destroyedBridgesCount = 0; + int undamagedBridgesCount = 0; + std::map ownedRecruitables; + std::map ownedBuildings; + + for (auto pTechno : *TechnoClass::Array) + { + if (pTechno->WhatAmI() == AbstractType::Building) + { + if (pTechno->Owner == pHouse) + { + ++ownedBuildings[static_cast(pTechno->GetTechnoType())]; + } + else + { + auto pBuilding = static_cast(pTechno); + if (pBuilding && pBuilding->Type->BridgeRepairHut) + { + CellStruct cell = pTechno->GetCell()->MapCoords; + + if (MapClass::Instance->IsLinkedBridgeDestroyed(cell)) + destroyedBridgesCount++; + else + undamagedBridgesCount++; + } + } + + continue; + } + + auto* pFoot = static_cast(pTechno); + + if (!pFoot + || !pTechno->IsAlive + || pTechno->Health <= 0 + || !pTechno->IsOnMap // Note: underground movement is considered "IsOnMap == false" + || pTechno->Transporter + || pTechno->Absorbed + || !pFoot->CanBeRecruited(pHouse)) + { + continue; + } + + ++ownedRecruitables[pTechno->GetTechnoType()]; + } + + HouseClass* targetHouse = nullptr; + if (pHouse->EnemyHouseIndex >= 0) + targetHouse = HouseClass::Array->GetItem(pHouse->EnemyHouseIndex); + + bool onlyCheckImportantTriggers = false; + + // Gather all the trigger candidates into one place for posterior fast calculations + for (auto const pTrigger : *AITriggerTypeClass::Array) + { + if (!pTrigger) + continue; + + int triggerHouse = pTrigger->HouseIndex; + int triggerSide = pTrigger->SideIndex; + + // Ignore the deactivated triggers + if (pTrigger->IsEnabled) + { + // The trigger must be compatible with the owner + if ((triggerHouse == -1 || houseTypeIdx == triggerHouse) && (triggerSide == 0 || sideTypeIdx == triggerSide)) + { + // "ConditionType=-1" will be skipped, always is valid + if ((int)pTrigger->ConditionType >= 0) + { + if ((int)pTrigger->ConditionType == 0) + { + // Simulate case 0: "enemy owns" + if (!pTrigger->ConditionObject) + continue; + + std::vector list; + list.push_back(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 1) + { + // Simulate case 1: "house owns" + if (!pTrigger->ConditionObject) + continue; + + std::vector list; + list.push_back(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 7) + { + // Simulate case 7: "civilian owns" + if (!pTrigger->ConditionObject) + continue; + + std::vector list; + list.push_back(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 8) + { + // Simulate case 0: "enemy owns" but instead of restrict it against the main enemy house it is done against all enemies + if (!pTrigger->ConditionObject) + continue; + + std::vector list; + list.push_back(pTrigger->ConditionObject); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 9) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 10) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 11) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 12) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 13) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 14) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 14: Like in case 9 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 15) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 15: Like in case 10 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 16) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 16: Like in case 11 but instead of meet any comparison now is required all. + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 17) + { + if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + { + // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies + // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) + std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); + bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); + + if (!isConditionMet) + continue; + } + } + else if ((int)pTrigger->ConditionType == 18) + { + // New case 18: Check destroyed bridges + bool isConditionMet = TeamExt::CountConditionMet(pTrigger, destroyedBridgesCount); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 19) + { + // New case 19: Check undamaged bridges + bool isConditionMet = TeamExt::CountConditionMet(pTrigger, undamagedBridgesCount); + + if (!isConditionMet) + continue; + } + else + { + // Other cases from vanilla game + if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) + continue; + } + } + + // All triggers below 5000 in current weight will get discarded if this mode is enabled + if (onlyCheckImportantTriggers) + { + if (pTrigger->Weight_Current < 5000) + continue; + } + + auto pTriggerTeam1Type = pTrigger->Team1; + if (!pTriggerTeam1Type) + continue; + + // No more defensive teams needed + if (pTriggerTeam1Type->IsBaseDefense && hasReachedMaxDefensiveTeamsLimit) + continue; + + // If this type of Team reached the max then skip it + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam1Type) + count++; + } + + if (count >= pTriggerTeam1Type->Max) + continue; + + teamCategory teamIsCategory = teamCategory::None; + + // Analyze what kind of category is this main team if the feature is enabled + if (splitTriggersByCategory) + { + //Debug::Log("DEBUG: TaskForce [%s] members:\n", pTriggerTeam1Type->TaskForce->ID); + // TaskForces are limited to 6 entries + for (int i = 0; i < 6; i++) + { + auto entry = pTriggerTeam1Type->TaskForce->Entries[i]; + teamCategory entryIsCategory = teamCategory::Ground; + + if (entry.Amount > 0) + { + if (!entry.Type) + continue; + + if (entry.Type->WhatAmI() == AbstractType::AircraftType + || entry.Type->ConsideredAircraft) + { + // This unit is from air category + entryIsCategory = teamCategory::Air; + //Debug::Log("\t[%s](%d) is in AIR category.\n", entry.Type->ID, entry.Amount); + } + else + { + auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(entry.Type); + if (!pTechnoTypeExt) + continue; + + if (pTechnoTypeExt->ConsideredNaval + || (entry.Type->Naval + && (entry.Type->MovementZone != MovementZone::Amphibious + && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer + && entry.Type->MovementZone != MovementZone::AmphibiousCrusher))) + { + // This unit is from naval category + entryIsCategory = teamCategory::Naval; + //Debug::Log("\t[%s](%d) is in NAVAL category.\n", entry.Type->ID, entry.Amount); + } + + if (pTechnoTypeExt->ConsideredVehicle + || (entryIsCategory != teamCategory::Naval + && entryIsCategory != teamCategory::Air)) + { + // This unit is from ground category + entryIsCategory = teamCategory::Ground; + //Debug::Log("\t[%s](%d) is in GROUND category.\n", entry.Type->ID, entry.Amount); + } + } + + // if a team have multiple categories it will be a mixed category + teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == entryIsCategory ? entryIsCategory : teamCategory::Unclassified; + + if (teamIsCategory == teamCategory::Unclassified) + break; + } + else + { + break; + } + } + + //Debug::Log("DEBUG: This team is a category %d (1:Ground, 2:Air, 3:Naval, 4:Mixed).\n", teamIsCategory); + // Si existe este valor y el team es MIXTO se sobreescribe el tipo de categoría + if (teamIsCategory == teamCategory::Unclassified + && mergeUnclassifiedCategoryWith >= 0) + { + //Debug::Log("DEBUG: MIXED category forced to work as category %d.\n", mergeUnclassifiedCategoryWith); + teamIsCategory = (teamCategory)mergeUnclassifiedCategoryWith; + } + } + + bool allObjectsCanBeBuiltOrRecruited = true; + + if (pTriggerTeam1Type->Autocreate) + { + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + { + // Check if each unit in the taskforce meets the structure prerequisites + if (entry.Amount > 0) + { + if (!entry.Type) + continue; + + TechnoTypeClass* object = entry.Type; + bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildings, false); + + if (!canBeBuilt) + { + allObjectsCanBeBuiltOrRecruited = false; + break; + } + } + else + { + break; + } + } + } + else + { + allObjectsCanBeBuiltOrRecruited = false; + } + + if (!allObjectsCanBeBuiltOrRecruited && pTriggerTeam1Type->Recruiter) + { + allObjectsCanBeBuiltOrRecruited = true; + + for (auto entry : pTriggerTeam1Type->TaskForce->Entries) + { + // Check if each unit in the taskforce has the available recruitable units in the map + if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) + { + if (ownedRecruitables.count(entry.Type) < entry.Amount) + { + allObjectsCanBeBuiltOrRecruited = false; + break; + } + } + } + } + + // We can't let AI cheat in this trigger because doesn't have the required tech tree available + if (!allObjectsCanBeBuiltOrRecruited) + continue; + + // Special case: triggers become very important if they reach the max priority (value 5000). + // They get stored in a elitist list and all previous triggers are discarded + if (pTrigger->Weight_Current >= 5000 && !onlyCheckImportantTriggers) + { + // First time only + if (validTriggerCandidates.Count > 0) + { + validTriggerCandidates.Clear(); + validTriggerCandidatesGroundOnly.Clear(); + validTriggerCandidatesNavalOnly.Clear(); + validTriggerCandidatesAirOnly.Clear(); + validTriggerCandidatesUnclassifiedOnly.Clear(); + validCategory = teamCategory::None; + } + + // Reset the current ones and now only will be added important triggers to the list + onlyCheckImportantTriggers = true; + totalWeight = 0.0; + splitTriggersByCategory = false; // VIP teams breaks the categories logic (on purpose) + } + + // Passed all checks, save this trigger for later. + // The idea behind this is to simulate an ordered list of weights and once we throw the dice we'll know the winner trigger: More weight means more possibilities to be selected. + totalWeight += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + TriggerElementWeight item; + item.Trigger = pTrigger; + item.Weight = totalWeight; + item.Category = teamIsCategory; + + validTriggerCandidates.AddItem(item); + + if (splitTriggersByCategory) + { + TriggerElementWeight itemGroundOnly; + TriggerElementWeight itemAirOnly; + TriggerElementWeight itemNavalOnly; + TriggerElementWeight itemUnclassifiedOnly; + + switch (teamIsCategory) + { + case teamCategory::Ground: + totalWeightGroundOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemGroundOnly.Trigger = pTrigger; + itemGroundOnly.Weight = totalWeightGroundOnly; + itemGroundOnly.Category = teamIsCategory; + + validTriggerCandidatesGroundOnly.AddItem(itemGroundOnly); + totalGroundCategoryTriggers++; + break; + + case teamCategory::Air: + totalWeightAirOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemAirOnly.Trigger = pTrigger; + itemAirOnly.Weight = totalWeightAirOnly; + itemAirOnly.Category = teamIsCategory; + + validTriggerCandidatesAirOnly.AddItem(itemAirOnly); + totalAirCategoryTriggers++; + break; + + case teamCategory::Naval: + totalWeightNavalOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemNavalOnly.Trigger = pTrigger; + itemNavalOnly.Weight = totalWeightNavalOnly; + itemNavalOnly.Category = teamIsCategory; + + validTriggerCandidatesNavalOnly.AddItem(itemNavalOnly); + totalNavalCategoryTriggers++; + break; + + case teamCategory::Unclassified: + totalWeightUnclassifiedOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; + + itemUnclassifiedOnly.Trigger = pTrigger; + itemUnclassifiedOnly.Weight = totalWeightUnclassifiedOnly; + itemUnclassifiedOnly.Category = teamIsCategory; + + validTriggerCandidatesUnclassifiedOnly.AddItem(itemUnclassifiedOnly); + totalUnclassifiedCategoryTriggers++; + break; + + default: + break; + } + } + } + } + } + + if (splitTriggersByCategory) + { + switch (validCategory) + { + case teamCategory::Ground: + Debug::Log("DEBUG: This time only will be picked GROUND teams.\n"); + break; + + case teamCategory::Unclassified: + Debug::Log("DEBUG: This time only will be picked MIXED teams.\n"); + break; + + case teamCategory::Naval: + Debug::Log("DEBUG: This time only will be picked NAVAL teams.\n"); + break; + + case teamCategory::Air: + Debug::Log("DEBUG: This time only will be picked AIR teams.\n"); + break; + + default: + Debug::Log("DEBUG: This time teams categories are DISABLED.\n"); + break; + } + } + + if (validTriggerCandidates.Count == 0) + { + Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers for now. A new attempt will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + if ((validCategory == teamCategory::Ground && totalGroundCategoryTriggers == 0) + || (validCategory == teamCategory::Unclassified && totalUnclassifiedCategoryTriggers == 0) + || (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) + || (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) + { + Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + + if (!isFallbackEnabled) + return SkipCode; + + Debug::Log("... but fallback mode is enabled so now will be checked all available triggers.\n"); + validCategory = teamCategory::None; + } + + AITriggerTypeClass* selectedTrigger = nullptr; + double weightDice = 0.0; + double lastWeight = 0.0; + bool found = false; + + switch (validCategory) + { + case teamCategory::None: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidates) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ + + for (auto element : validTriggerCandidates) + { + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + selectedTrigger = element.Trigger; + found = true; + } + } + break; + + case teamCategory::Ground: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesGroundOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ + + for (auto element : validTriggerCandidatesGroundOnly) + { + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + selectedTrigger = element.Trigger; + found = true; + } + } + break; + + case teamCategory::Unclassified: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesUnclassifiedOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ + + for (auto element : validTriggerCandidatesUnclassifiedOnly) + { + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + selectedTrigger = element.Trigger; + found = true; + } + } + break; + + case teamCategory::Naval: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesNavalOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ + + for (auto element : validTriggerCandidatesNavalOnly) + { + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + selectedTrigger = element.Trigger; + found = true; + } + } + break; + + case teamCategory::Air: + weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; + /*Debug::Log("Weight Dice: %f\n", weightDice); + + // Debug + Debug::Log("DEBUG: Candidate AI triggers list:\n"); + for (TriggerElementWeight element : validTriggerCandidatesAirOnly) + { + Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + }*/ + + for (auto element : validTriggerCandidatesAirOnly) + { + lastWeight = element.Weight; + + if (weightDice < element.Weight && !found) + { + selectedTrigger = element.Trigger; + found = true; + } + } + break; + + default: + break; + } + + if (!selectedTrigger) + { + Debug::Log("AI Team Selector: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + return SkipCode; + } + + if (selectedTrigger->Weight_Current >= 5000.0 + && selectedTrigger->Weight_Minimum <= 4999.0) + { + // Next time this trigger will be out of the elitist triggers list + selectedTrigger->Weight_Current = 4999.0; + } + + // We have a winner trigger here + Debug::Log("AI Team Selector: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); + + // Team 1 creation + auto pTriggerTeam1Type = selectedTrigger->Team1; + if (pTriggerTeam1Type) + { + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam1Type) + count++; + } + + if (count < pTriggerTeam1Type->Max) + { + if (TeamClass* newTeam1 = pTriggerTeam1Type->CreateTeam(pHouse)) + newTeam1->NeedsToDisappear = false; + } + } + + // Team 2 creation (if set) + auto pTriggerTeam2Type = selectedTrigger->Team2; + if (pTriggerTeam2Type) + { + int count = 0; + + for (auto team : activeTeamsList) + { + if (team->Type == pTriggerTeam2Type) + count++; + } + + if (count < pTriggerTeam2Type->Max) + { + if (TeamClass* newTeam2 = pTriggerTeam2Type->CreateTeam(pHouse)) + newTeam2->NeedsToDisappear = false; + } + } + } + + return SkipCode; +} + +bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list) +{ + bool result = false; + int counter = 0; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && ((!allies && pObject->Owner == pHouse) || (allies && pHouse != pObject->Owner && pHouse->IsAlliedWith(pObject->Owner))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) +{ + bool result = false; + int counter = 0; + + if (pEnemy && pHouse->IsAlliedWith(pEnemy) && !onlySelectedEnemy) + pEnemy = nullptr; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner != pHouse + && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) +{ + bool result = false; + int counter = 0; + + for (auto pHouse : *HouseClass::Array) + { + if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) + continue; + + // Count all objects of the list, like an OR operator + for (auto pItem : list) + { + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner == pHouse + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} + +bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list) +{ + bool result = true; + + if (list.size() == 0) + return false; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!result) + break; + + int counter = 0; + result = true; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject && + pObject->IsAlive && + pObject->Health > 0 && + pObject->Owner == pHouse && + pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + return result; +} + +bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list) +{ + bool result = true; + + if (pEnemy && pHouse->IsAlliedWith(pEnemy)) + pEnemy = nullptr; + + if (list.size() == 0) + return false; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!result) + break; + + int counter = 0; + result = true; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject + && pObject->IsAlive + && pObject->Health > 0 + && pObject->Owner != pHouse + && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) + && !pObject->Owner->Type->MultiplayPassive + && pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = counter < pThis->Conditions->ComparatorType; + break; + case 1: + result = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + result = counter == pThis->Conditions->ComparatorType; + break; + case 3: + result = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + result = counter > pThis->Conditions->ComparatorType; + break; + case 5: + result = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + return result; +} + +bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list) +{ + bool result = true; + + if (list.size() == 0) + return false; + + // Any neutral house should be capable to meet the prerequisites + for (auto pHouse : *HouseClass::Array) + { + if (!result) + break; + + bool foundAll = true; + + if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) + continue; + + // Count all objects of the list, like an AND operator + for (auto pItem : list) + { + if (!foundAll) + break; + + int counter = 0; + + for (auto pObject : *TechnoClass::Array) + { + if (pObject && + pObject->IsAlive && + pObject->Health > 0 && + pObject->Owner == pHouse && + pObject->GetTechnoType() == pItem) + { + counter++; + } + } + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + foundAll = counter < pThis->Conditions->ComparatorType; + break; + case 1: + foundAll = counter <= pThis->Conditions->ComparatorType; + break; + case 2: + foundAll = counter == pThis->Conditions->ComparatorType; + break; + case 3: + foundAll = counter >= pThis->Conditions->ComparatorType; + break; + case 4: + foundAll = counter > pThis->Conditions->ComparatorType; + break; + case 5: + foundAll = counter != pThis->Conditions->ComparatorType; + break; + default: + break; + } + } + + if (!foundAll) + result = false; + } + + return result; +} + +bool TeamExt::CountConditionMet(AITriggerTypeClass* pThis, int nObjects) +{ + bool result = true; + + if (nObjects < 0) + return false; + + switch (pThis->Conditions->ComparatorOperand) + { + case 0: + result = nObjects < pThis->Conditions->ComparatorType; + break; + case 1: + result = nObjects <= pThis->Conditions->ComparatorType; + break; + case 2: + result = nObjects == pThis->Conditions->ComparatorType; + break; + case 3: + result = nObjects >= pThis->Conditions->ComparatorType; + break; + case 4: + result = nObjects > pThis->Conditions->ComparatorType; + break; + case 5: + result = nObjects != pThis->Conditions->ComparatorType; + break; + default: + break; + } + + return result; +} diff --git a/src/Ext/Team/Body.cpp b/src/Ext/Team/Body.cpp index b809226c31..666d08e666 100644 --- a/src/Ext/Team/Body.cpp +++ b/src/Ext/Team/Body.cpp @@ -93,1322 +93,3 @@ DEFINE_HOOK(0x6EC55A, TeamClass_Save_Suffix, 0x5) TeamExt::ExtMap.SaveStatic(); return 0; } - -DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) -{ - enum { UseOriginalSelector = 0x4F8A63, SkipCode = 0x4F8B08 }; - - GET(HouseClass*, pHouse, ESI); - - bool houseIsHuman = pHouse->IsHumanPlayer; - - if (SessionClass::IsCampaign()) - houseIsHuman = pHouse->IsHumanPlayer || pHouse->IsInPlayerControl; - - if (houseIsHuman || pHouse->Type->MultiplayPassive) - return SkipCode; - - auto pHouseTypeExt = HouseTypeExt::ExtMap.Find(pHouse->Type); - if (!pHouseTypeExt) - return SkipCode; - - if (!RulesExt::Global()->NewTeamsSelector) - return UseOriginalSelector; - - // Reset Team selection countdown - int countdown = RulesClass::Instance->TeamDelays[(int)pHouse->AIDifficulty]; - pHouse->TeamDelayTimer.Start(countdown); - - int totalActiveTeams = 0; - int activeTeams = 0; - - int totalGroundCategoryTriggers = 0; - int totalUnclassifiedCategoryTriggers = 0; - int totalNavalCategoryTriggers = 0; - int totalAirCategoryTriggers = 0; - - DynamicVectorClass validTriggerCandidates; - DynamicVectorClass validTriggerCandidatesGroundOnly; - DynamicVectorClass validTriggerCandidatesNavalOnly; - DynamicVectorClass validTriggerCandidatesAirOnly; - DynamicVectorClass validTriggerCandidatesUnclassifiedOnly; - - int dice = ScenarioClass::Instance->Random.RandomRanged(1, 100); - - // This house must have the triggers enabled - if (dice <= pHouse->RatioAITriggerTeam && pHouse->AITriggersActive) - { - bool splitTriggersByCategory = RulesExt::Global()->NewTeamsSelector_SplitTriggersByCategory; - bool isFallbackEnabled = RulesExt::Global()->NewTeamsSelector_EnableFallback; - teamCategory validCategory = teamCategory::None; - int mergeUnclassifiedCategoryWith = -1; - - double percentageUnclassifiedTriggers = 0.0; - double percentageGroundTriggers = 0.0; - double percentageNavalTriggers = 0.0; - double percentageAirTriggers = 0.0; - - if (splitTriggersByCategory) - { - mergeUnclassifiedCategoryWith = pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.isset() ? pHouseTypeExt->NewTeamsSelector_MergeUnclassifiedCategoryWith.Get() : RulesExt::Global()->NewTeamsSelector_MergeUnclassifiedCategoryWith; // Should mixed teams be merged into another category? - percentageUnclassifiedTriggers = pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_UnclassifiedCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_UnclassifiedCategoryPercentage; // Mixed teams - percentageGroundTriggers = pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_GroundCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_GroundCategoryPercentage; // Only ground - percentageNavalTriggers = pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_NavalCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_NavalCategoryPercentage; // Only Naval=yes - percentageAirTriggers = pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.isset() ? pHouseTypeExt->NewTeamsSelector_AirCategoryPercentage.Get() : RulesExt::Global()->NewTeamsSelector_AirCategoryPercentage; // Only Aircrafts & jumpjets - - // Merge mixed category with another category, if set - if (mergeUnclassifiedCategoryWith >= 0) - { - switch (mergeUnclassifiedCategoryWith) - { - case (int)teamCategory::Ground: - percentageGroundTriggers += percentageUnclassifiedTriggers; - break; - - case (int)teamCategory::Air: - percentageAirTriggers += percentageUnclassifiedTriggers; - break; - - case (int)teamCategory::Naval: - percentageNavalTriggers += percentageUnclassifiedTriggers; - break; - - default: - break; - } - - percentageUnclassifiedTriggers = 0.0; - } - - percentageUnclassifiedTriggers = percentageUnclassifiedTriggers < 0.0 || percentageUnclassifiedTriggers > 1.0 ? 0.0 : percentageUnclassifiedTriggers; - percentageGroundTriggers = percentageGroundTriggers < 0.0 || percentageGroundTriggers > 1.0 ? 0.0 : percentageGroundTriggers; - percentageNavalTriggers = percentageNavalTriggers < 0.0 || percentageNavalTriggers > 1.0 ? 0.0 : percentageNavalTriggers; - percentageAirTriggers = percentageAirTriggers < 0.0 || percentageAirTriggers > 1.0 ? 0.0 : percentageAirTriggers; - - double totalPercengates = percentageUnclassifiedTriggers + percentageGroundTriggers + percentageNavalTriggers + percentageAirTriggers; - if (totalPercengates > 1.0 || totalPercengates <= 0.0) - splitTriggersByCategory = false; - - - if (splitTriggersByCategory) - { - int categoryDice = ScenarioClass::Instance->Random.RandomRanged(1, 100); - int unclassifiedValue = (int)(percentageUnclassifiedTriggers * 100.0); - int groundValue = (int)(percentageGroundTriggers * 100.0); - int airValue = (int)(percentageAirTriggers * 100.0); - int navalValue = (int)(percentageNavalTriggers * 100.0); - - // Pick what type of team will be selected in this round - if (percentageUnclassifiedTriggers > 0.0 && categoryDice <= unclassifiedValue) - { - validCategory = teamCategory::Unclassified; - Debug::Log("New AI team category selection: dice %d <= %d (MIXED)\n", categoryDice, unclassifiedValue); - } - else if (percentageGroundTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue)) - { - validCategory = teamCategory::Ground; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + GROUND: %d%%)\n", categoryDice, (unclassifiedValue + groundValue), unclassifiedValue, groundValue); - } - else if (percentageAirTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue)) - { - validCategory = teamCategory::Air; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + AIR: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue), unclassifiedValue, groundValue, airValue); - } - else if (percentageNavalTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue + navalValue)) - { - validCategory = teamCategory::Naval; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + air: %d%% + NAVAL: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue + navalValue), unclassifiedValue, groundValue, airValue, navalValue); - } - else - { - // If the sum of all percentages is less than 100% then that empty space will work like "no categories" - splitTriggersByCategory = false; - } - } - } - - int parentCountryTypeIdx = pHouse->Type->FindParentCountryIndex(); // ParentCountry can change the House in a SP map - int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based - int houseIdx = pHouse->ArrayIndex; - - int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; - int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based - int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based - - auto houseDifficulty = pHouse->AIDifficulty; - int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); - int activeDefenseTeamsCount = 0; - int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); - double totalWeight = 0.0; - double totalWeightGroundOnly = 0.0; - double totalWeightNavalOnly = 0.0; - double totalWeightAirOnly = 0.0; - double totalWeightUnclassifiedOnly = 0.0; - - // Check if the running teams by the house already reached all the limits - DynamicVectorClass activeTeamsList; - - for (auto const pRunningTeam : *TeamClass::Array) - { - totalActiveTeams++; - int teamHouseIdx = pRunningTeam->Owner->ArrayIndex; - - if (teamHouseIdx != houseIdx) - continue; - - activeTeamsList.AddItem(pRunningTeam); - - if (pRunningTeam->Type->IsBaseDefense && activeDefenseTeamsCount < maxBaseDefenseTeams) - activeDefenseTeamsCount++; - } - - activeTeams = activeTeamsList.Count; - - /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); - for (auto team : activeTeamsList) - { - Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); - Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); - int i = 0; - - for (auto entry : team->Type->TaskForce->Entries) - { - if (entry.Type && entry.Amount > 0) - { - if (entry.Type) - Debug::Log("\t[%s]: %d / %d\n", entry.Type->ID, team->CountObjects[i], entry.Amount); - } - - i++; - } - } - Debug::Log("=====================\n");*/ - - // We will use these values for discarding triggers - bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; - bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; - - if (hasReachedMaxDefensiveTeamsLimit) - Debug::Log("DEBUG: House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); - - if (hasReachedMaxTeamsLimit) - { - Debug::Log("DEBUG: House [%s] (idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); - return SkipCode; - } - - int destroyedBridgesCount = 0; - int undamagedBridgesCount = 0; - std::map ownedRecruitables; - std::map ownedBuildings; - - for (auto pTechno : *TechnoClass::Array) - { - if (pTechno->WhatAmI() == AbstractType::Building) - { - if (pTechno->Owner == pHouse) - { - ++ownedBuildings[static_cast(pTechno->GetTechnoType())]; - } - else - { - auto pBuilding = static_cast(pTechno); - if (pBuilding && pBuilding->Type->BridgeRepairHut) - { - CellStruct cell = pTechno->GetCell()->MapCoords; - - if (MapClass::Instance->IsLinkedBridgeDestroyed(cell)) - destroyedBridgesCount++; - else - undamagedBridgesCount++; - } - } - - continue; - } - - auto* pFoot = static_cast(pTechno); - - if (!pFoot - || !pTechno->IsAlive - || pTechno->Health <= 0 - || !pTechno->IsOnMap // Note: underground movement is considered "IsOnMap == false" - || pTechno->Transporter - || pTechno->Absorbed - || !pFoot->CanBeRecruited(pHouse)) - { - continue; - } - - ++ownedRecruitables[pTechno->GetTechnoType()]; - } - - HouseClass* targetHouse = nullptr; - if (pHouse->EnemyHouseIndex >= 0) - targetHouse = HouseClass::Array->GetItem(pHouse->EnemyHouseIndex); - - bool onlyCheckImportantTriggers = false; - - // Gather all the trigger candidates into one place for posterior fast calculations - for (auto const pTrigger : *AITriggerTypeClass::Array) - { - if (!pTrigger) - continue; - - int triggerHouse = pTrigger->HouseIndex; - int triggerSide = pTrigger->SideIndex; - - // Ignore the deactivated triggers - if (pTrigger->IsEnabled) - { - // The trigger must be compatible with the owner - if ((triggerHouse == -1 || houseTypeIdx == triggerHouse) && (triggerSide == 0 || sideTypeIdx == triggerSide)) - { - // "ConditionType=-1" will be skipped, always is valid - if ((int)pTrigger->ConditionType >= 0) - { - if ((int)pTrigger->ConditionType == 0) - { - // Simulate case 0: "enemy owns" - if (!pTrigger->ConditionObject) - continue; - - std::vector list; - list.push_back(pTrigger->ConditionObject); - bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, true, list); - - if (!isConditionMet) - continue; - } - else if ((int)pTrigger->ConditionType == 1) - { - // Simulate case 1: "house owns" - if (!pTrigger->ConditionObject) - continue; - - std::vector list; - list.push_back(pTrigger->ConditionObject); - bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); - - if (!isConditionMet) - continue; - } - else if ((int)pTrigger->ConditionType == 7) - { - // Simulate case 7: "civilian owns" - if (!pTrigger->ConditionObject) - continue; - - std::vector list; - list.push_back(pTrigger->ConditionObject); - bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); - - if (!isConditionMet) - continue; - } - else if ((int)pTrigger->ConditionType == 8) - { - // Simulate case 0: "enemy owns" but instead of restrict it against the main enemy house it is done against all enemies - if (!pTrigger->ConditionObject) - continue; - - std::vector list; - list.push_back(pTrigger->ConditionObject); - bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); - - if (!isConditionMet) - continue; - } - else if ((int)pTrigger->ConditionType == 9) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, targetHouse, false, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 10) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 11) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::NeutralOwns(pTrigger, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 12) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::EnemyOwns(pTrigger, pHouse, nullptr, false, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 13) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, true, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 14) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 14: Like in case 9 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, targetHouse, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 15) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 15: Like in case 10 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::HouseOwnsAll(pTrigger, pHouse, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 16) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 16: Like in case 11 but instead of meet any comparison now is required all. - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::NeutralOwnsAll(pTrigger, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 17) - { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) - { - // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies - // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) - std::vector list = RulesExt::Global()->AITargetTypesLists.at(pTrigger->Conditions[3].ComparatorOperand); - bool isConditionMet = TeamExt::EnemyOwnsAll(pTrigger, pHouse, nullptr, list); - - if (!isConditionMet) - continue; - } - } - else if ((int)pTrigger->ConditionType == 18) - { - // New case 18: Check destroyed bridges - bool isConditionMet = TeamExt::CountConditionMet(pTrigger, destroyedBridgesCount); - - if (!isConditionMet) - continue; - } - else if ((int)pTrigger->ConditionType == 19) - { - // New case 19: Check undamaged bridges - bool isConditionMet = TeamExt::CountConditionMet(pTrigger, undamagedBridgesCount); - - if (!isConditionMet) - continue; - } - else - { - // Other cases from vanilla game - if (!pTrigger->ConditionMet(pHouse, targetHouse, hasReachedMaxDefensiveTeamsLimit)) - continue; - } - } - - // All triggers below 5000 in current weight will get discarded if this mode is enabled - if (onlyCheckImportantTriggers) - { - if (pTrigger->Weight_Current < 5000) - continue; - } - - auto pTriggerTeam1Type = pTrigger->Team1; - if (!pTriggerTeam1Type) - continue; - - // No more defensive teams needed - if (pTriggerTeam1Type->IsBaseDefense && hasReachedMaxDefensiveTeamsLimit) - continue; - - // If this type of Team reached the max then skip it - int count = 0; - - for (auto team : activeTeamsList) - { - if (team->Type == pTriggerTeam1Type) - count++; - } - - if (count >= pTriggerTeam1Type->Max) - continue; - - teamCategory teamIsCategory = teamCategory::None; - - // Analyze what kind of category is this main team if the feature is enabled - if (splitTriggersByCategory) - { - //Debug::Log("DEBUG: TaskForce [%s] members:\n", pTriggerTeam1Type->TaskForce->ID); - // TaskForces are limited to 6 entries - for (int i = 0; i < 6; i++) - { - auto entry = pTriggerTeam1Type->TaskForce->Entries[i]; - teamCategory entryIsCategory = teamCategory::Ground; - - if (entry.Amount > 0) - { - if (!entry.Type) - continue; - - if (entry.Type->WhatAmI() == AbstractType::AircraftType - || entry.Type->ConsideredAircraft) - { - // This unit is from air category - entryIsCategory = teamCategory::Air; - //Debug::Log("\t[%s](%d) is in AIR category.\n", entry.Type->ID, entry.Amount); - } - else - { - auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(entry.Type); - if (!pTechnoTypeExt) - continue; - - if (pTechnoTypeExt->ConsideredNaval - || (entry.Type->Naval - && (entry.Type->MovementZone != MovementZone::Amphibious - && entry.Type->MovementZone != MovementZone::AmphibiousDestroyer - && entry.Type->MovementZone != MovementZone::AmphibiousCrusher))) - { - // This unit is from naval category - entryIsCategory = teamCategory::Naval; - //Debug::Log("\t[%s](%d) is in NAVAL category.\n", entry.Type->ID, entry.Amount); - } - - if (pTechnoTypeExt->ConsideredVehicle - || (entryIsCategory != teamCategory::Naval - && entryIsCategory != teamCategory::Air)) - { - // This unit is from ground category - entryIsCategory = teamCategory::Ground; - //Debug::Log("\t[%s](%d) is in GROUND category.\n", entry.Type->ID, entry.Amount); - } - } - - // if a team have multiple categories it will be a mixed category - teamIsCategory = teamIsCategory == teamCategory::None || teamIsCategory == entryIsCategory ? entryIsCategory : teamCategory::Unclassified; - - if (teamIsCategory == teamCategory::Unclassified) - break; - } - else - { - break; - } - } - - //Debug::Log("DEBUG: This team is a category %d (1:Ground, 2:Air, 3:Naval, 4:Mixed).\n", teamIsCategory); - // Si existe este valor y el team es MIXTO se sobreescribe el tipo de categoría - if (teamIsCategory == teamCategory::Unclassified - && mergeUnclassifiedCategoryWith >= 0) - { - //Debug::Log("DEBUG: MIXED category forced to work as category %d.\n", mergeUnclassifiedCategoryWith); - teamIsCategory = (teamCategory)mergeUnclassifiedCategoryWith; - } - } - - bool allObjectsCanBeBuiltOrRecruited = true; - - if (pTriggerTeam1Type->Autocreate) - { - for (auto entry : pTriggerTeam1Type->TaskForce->Entries) - { - // Check if each unit in the taskforce meets the structure prerequisites - if (entry.Amount > 0) - { - if (!entry.Type) - continue; - - TechnoTypeClass* object = entry.Type; - bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildings, false); - - if (!canBeBuilt) - { - allObjectsCanBeBuiltOrRecruited = false; - break; - } - } - else - { - break; - } - } - } - else - { - allObjectsCanBeBuiltOrRecruited = false; - } - - if (!allObjectsCanBeBuiltOrRecruited && pTriggerTeam1Type->Recruiter) - { - allObjectsCanBeBuiltOrRecruited = true; - - for (auto entry : pTriggerTeam1Type->TaskForce->Entries) - { - // Check if each unit in the taskforce has the available recruitable units in the map - if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) - { - if (ownedRecruitables.count(entry.Type) < entry.Amount) - { - allObjectsCanBeBuiltOrRecruited = false; - break; - } - } - } - } - - // We can't let AI cheat in this trigger because doesn't have the required tech tree available - if (!allObjectsCanBeBuiltOrRecruited) - continue; - - // Special case: triggers become very important if they reach the max priority (value 5000). - // They get stored in a elitist list and all previous triggers are discarded - if (pTrigger->Weight_Current >= 5000 && !onlyCheckImportantTriggers) - { - // First time only - if (validTriggerCandidates.Count > 0) - { - validTriggerCandidates.Clear(); - validTriggerCandidatesGroundOnly.Clear(); - validTriggerCandidatesNavalOnly.Clear(); - validTriggerCandidatesAirOnly.Clear(); - validTriggerCandidatesUnclassifiedOnly.Clear(); - validCategory = teamCategory::None; - } - - // Reset the current ones and now only will be added important triggers to the list - onlyCheckImportantTriggers = true; - totalWeight = 0.0; - splitTriggersByCategory = false; // VIP teams breaks the categories logic (on purpose) - } - - // Passed all checks, save this trigger for later. - // The idea behind this is to simulate an ordered list of weights and once we throw the dice we'll know the winner trigger: More weight means more possibilities to be selected. - totalWeight += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; - TriggerElementWeight item; - item.Trigger = pTrigger; - item.Weight = totalWeight; - item.Category = teamIsCategory; - - validTriggerCandidates.AddItem(item); - - if (splitTriggersByCategory) - { - TriggerElementWeight itemGroundOnly; - TriggerElementWeight itemAirOnly; - TriggerElementWeight itemNavalOnly; - TriggerElementWeight itemUnclassifiedOnly; - - switch (teamIsCategory) - { - case teamCategory::Ground: - totalWeightGroundOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; - - itemGroundOnly.Trigger = pTrigger; - itemGroundOnly.Weight = totalWeightGroundOnly; - itemGroundOnly.Category = teamIsCategory; - - validTriggerCandidatesGroundOnly.AddItem(itemGroundOnly); - totalGroundCategoryTriggers++; - break; - - case teamCategory::Air: - totalWeightAirOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; - - itemAirOnly.Trigger = pTrigger; - itemAirOnly.Weight = totalWeightAirOnly; - itemAirOnly.Category = teamIsCategory; - - validTriggerCandidatesAirOnly.AddItem(itemAirOnly); - totalAirCategoryTriggers++; - break; - - case teamCategory::Naval: - totalWeightNavalOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; - - itemNavalOnly.Trigger = pTrigger; - itemNavalOnly.Weight = totalWeightNavalOnly; - itemNavalOnly.Category = teamIsCategory; - - validTriggerCandidatesNavalOnly.AddItem(itemNavalOnly); - totalNavalCategoryTriggers++; - break; - - case teamCategory::Unclassified: - totalWeightUnclassifiedOnly += pTrigger->Weight_Current < 1.0 ? 1.0 : pTrigger->Weight_Current; - - itemUnclassifiedOnly.Trigger = pTrigger; - itemUnclassifiedOnly.Weight = totalWeightUnclassifiedOnly; - itemUnclassifiedOnly.Category = teamIsCategory; - - validTriggerCandidatesUnclassifiedOnly.AddItem(itemUnclassifiedOnly); - totalUnclassifiedCategoryTriggers++; - break; - - default: - break; - } - } - } - } - } - - if (splitTriggersByCategory) - { - switch (validCategory) - { - case teamCategory::Ground: - Debug::Log("DEBUG: This time only will be picked GROUND teams.\n"); - break; - - case teamCategory::Unclassified: - Debug::Log("DEBUG: This time only will be picked MIXED teams.\n"); - break; - - case teamCategory::Naval: - Debug::Log("DEBUG: This time only will be picked NAVAL teams.\n"); - break; - - case teamCategory::Air: - Debug::Log("DEBUG: This time only will be picked AIR teams.\n"); - break; - - default: - Debug::Log("DEBUG: This time teams categories are DISABLED.\n"); - break; - } - } - - if (validTriggerCandidates.Count == 0) - { - Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers for now. A new attempt will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); - return SkipCode; - } - - if ((validCategory == teamCategory::Ground && totalGroundCategoryTriggers == 0) - || (validCategory == teamCategory::Unclassified && totalUnclassifiedCategoryTriggers == 0) - || (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) - || (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) - { - Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); - - if (!isFallbackEnabled) - return SkipCode; - - Debug::Log("... but fallback mode is enabled so now will be checked all available triggers.\n"); - validCategory = teamCategory::None; - } - - AITriggerTypeClass* selectedTrigger = nullptr; - double weightDice = 0.0; - double lastWeight = 0.0; - bool found = false; - - switch (validCategory) - { - case teamCategory::None: - weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); - - // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); - for (TriggerElementWeight element : validTriggerCandidates) - { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); - }*/ - - for (auto element : validTriggerCandidates) - { - lastWeight = element.Weight; - - if (weightDice < element.Weight && !found) - { - selectedTrigger = element.Trigger; - found = true; - } - } - break; - - case teamCategory::Ground: - weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); - - // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); - for (TriggerElementWeight element : validTriggerCandidatesGroundOnly) - { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); - }*/ - - for (auto element : validTriggerCandidatesGroundOnly) - { - lastWeight = element.Weight; - - if (weightDice < element.Weight && !found) - { - selectedTrigger = element.Trigger; - found = true; - } - } - break; - - case teamCategory::Unclassified: - weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); - - // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); - for (TriggerElementWeight element : validTriggerCandidatesUnclassifiedOnly) - { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); - }*/ - - for (auto element : validTriggerCandidatesUnclassifiedOnly) - { - lastWeight = element.Weight; - - if (weightDice < element.Weight && !found) - { - selectedTrigger = element.Trigger; - found = true; - } - } - break; - - case teamCategory::Naval: - weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); - - // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); - for (TriggerElementWeight element : validTriggerCandidatesNavalOnly) - { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); - }*/ - - for (auto element : validTriggerCandidatesNavalOnly) - { - lastWeight = element.Weight; - - if (weightDice < element.Weight && !found) - { - selectedTrigger = element.Trigger; - found = true; - } - } - break; - - case teamCategory::Air: - weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); - - // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); - for (TriggerElementWeight element : validTriggerCandidatesAirOnly) - { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); - }*/ - - for (auto element : validTriggerCandidatesAirOnly) - { - lastWeight = element.Weight; - - if (weightDice < element.Weight && !found) - { - selectedTrigger = element.Trigger; - found = true; - } - } - break; - - default: - break; - } - - if (!selectedTrigger) - { - Debug::Log("AI Team Selector: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); - return SkipCode; - } - - if (selectedTrigger->Weight_Current >= 5000.0 - && selectedTrigger->Weight_Minimum <= 4999.0) - { - // Next time this trigger will be out of the elitist triggers list - selectedTrigger->Weight_Current = 4999.0; - } - - // We have a winner trigger here - Debug::Log("AI Team Selector: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); - - // Team 1 creation - auto pTriggerTeam1Type = selectedTrigger->Team1; - if (pTriggerTeam1Type) - { - int count = 0; - - for (auto team : activeTeamsList) - { - if (team->Type == pTriggerTeam1Type) - count++; - } - - if (count < pTriggerTeam1Type->Max) - { - if (TeamClass* newTeam1 = pTriggerTeam1Type->CreateTeam(pHouse)) - newTeam1->NeedsToDisappear = false; - } - } - - // Team 2 creation (if set) - auto pTriggerTeam2Type = selectedTrigger->Team2; - if (pTriggerTeam2Type) - { - int count = 0; - - for (auto team : activeTeamsList) - { - if (team->Type == pTriggerTeam2Type) - count++; - } - - if (count < pTriggerTeam2Type->Max) - { - if (TeamClass* newTeam2 = pTriggerTeam2Type->CreateTeam(pHouse)) - newTeam2->NeedsToDisappear = false; - } - } - } - - return SkipCode; -} - -bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool allies, std::vector list) -{ - bool result = false; - int counter = 0; - - // Count all objects of the list, like an OR operator - for (auto pItem : list) - { - for (auto pObject : *TechnoClass::Array) - { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && ((!allies && pObject->Owner == pHouse) || (allies && pHouse != pObject->Owner && pHouse->IsAlliedWith(pObject->Owner))) - && !pObject->Owner->Type->MultiplayPassive - && pObject->GetTechnoType() == pItem) - { - counter++; - } - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = counter < pThis->Conditions->ComparatorType; - break; - case 1: - result = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - result = counter == pThis->Conditions->ComparatorType; - break; - case 3: - result = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - result = counter > pThis->Conditions->ComparatorType; - break; - case 5: - result = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - - return result; -} - -bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) -{ - bool result = false; - int counter = 0; - - if (pEnemy && pHouse->IsAlliedWith(pEnemy) && !onlySelectedEnemy) - pEnemy = nullptr; - - // Count all objects of the list, like an OR operator - for (auto pItem : list) - { - for (auto pObject : *TechnoClass::Array) - { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner != pHouse - && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) - && !pObject->Owner->Type->MultiplayPassive - && pObject->GetTechnoType() == pItem) - { - counter++; - } - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = counter < pThis->Conditions->ComparatorType; - break; - case 1: - result = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - result = counter == pThis->Conditions->ComparatorType; - break; - case 3: - result = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - result = counter > pThis->Conditions->ComparatorType; - break; - case 5: - result = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - - return result; -} - -bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vector list) -{ - bool result = false; - int counter = 0; - - for (auto pHouse : *HouseClass::Array) - { - if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) - continue; - - // Count all objects of the list, like an OR operator - for (auto pItem : list) - { - for (auto pObject : *TechnoClass::Array) - { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner == pHouse - && pObject->GetTechnoType() == pItem) - { - counter++; - } - } - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = counter < pThis->Conditions->ComparatorType; - break; - case 1: - result = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - result = counter == pThis->Conditions->ComparatorType; - break; - case 3: - result = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - result = counter > pThis->Conditions->ComparatorType; - break; - case 5: - result = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - - return result; -} - -bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::vector list) -{ - bool result = true; - - if (list.size() == 0) - return false; - - // Count all objects of the list, like an AND operator - for (auto pItem : list) - { - if (!result) - break; - - int counter = 0; - result = true; - - for (auto pObject : *TechnoClass::Array) - { - if (pObject && - pObject->IsAlive && - pObject->Health > 0 && - pObject->Owner == pHouse && - pObject->GetTechnoType() == pItem) - { - counter++; - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = counter < pThis->Conditions->ComparatorType; - break; - case 1: - result = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - result = counter == pThis->Conditions->ComparatorType; - break; - case 3: - result = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - result = counter > pThis->Conditions->ComparatorType; - break; - case 5: - result = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - } - - return result; -} - -bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, std::vector list) -{ - bool result = true; - - if (pEnemy && pHouse->IsAlliedWith(pEnemy)) - pEnemy = nullptr; - - if (list.size() == 0) - return false; - - // Count all objects of the list, like an AND operator - for (auto pItem : list) - { - if (!result) - break; - - int counter = 0; - result = true; - - for (auto pObject : *TechnoClass::Array) - { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner != pHouse - && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) - && !pObject->Owner->Type->MultiplayPassive - && pObject->GetTechnoType() == pItem) - { - counter++; - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = counter < pThis->Conditions->ComparatorType; - break; - case 1: - result = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - result = counter == pThis->Conditions->ComparatorType; - break; - case 3: - result = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - result = counter > pThis->Conditions->ComparatorType; - break; - case 5: - result = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - } - - return result; -} - -bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vector list) -{ - bool result = true; - - if (list.size() == 0) - return false; - - // Any neutral house should be capable to meet the prerequisites - for (auto pHouse : *HouseClass::Array) - { - if (!result) - break; - - bool foundAll = true; - - if (_stricmp(SideClass::Array->GetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) - continue; - - // Count all objects of the list, like an AND operator - for (auto pItem : list) - { - if (!foundAll) - break; - - int counter = 0; - - for (auto pObject : *TechnoClass::Array) - { - if (pObject && - pObject->IsAlive && - pObject->Health > 0 && - pObject->Owner == pHouse && - pObject->GetTechnoType() == pItem) - { - counter++; - } - } - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - foundAll = counter < pThis->Conditions->ComparatorType; - break; - case 1: - foundAll = counter <= pThis->Conditions->ComparatorType; - break; - case 2: - foundAll = counter == pThis->Conditions->ComparatorType; - break; - case 3: - foundAll = counter >= pThis->Conditions->ComparatorType; - break; - case 4: - foundAll = counter > pThis->Conditions->ComparatorType; - break; - case 5: - foundAll = counter != pThis->Conditions->ComparatorType; - break; - default: - break; - } - } - - if (!foundAll) - result = false; - } - - return result; -} - -bool TeamExt::CountConditionMet(AITriggerTypeClass* pThis, int nObjects) -{ - bool result = true; - - if (nObjects < 0) - return false; - - switch (pThis->Conditions->ComparatorOperand) - { - case 0: - result = nObjects < pThis->Conditions->ComparatorType; - break; - case 1: - result = nObjects <= pThis->Conditions->ComparatorType; - break; - case 2: - result = nObjects == pThis->Conditions->ComparatorType; - break; - case 3: - result = nObjects >= pThis->Conditions->ComparatorType; - break; - case 4: - result = nObjects > pThis->Conditions->ComparatorType; - break; - case 5: - result = nObjects != pThis->Conditions->ComparatorType; - break; - default: - break; - } - - return result; -} From 7e5f46166686b89217e4741ae7bb11ad03489159 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 29 Apr 2024 09:10:17 +0200 Subject: [PATCH 25/34] Moved code into its own file --- Phobos.vcxproj | 1 + src/Ext/Scenario/Hooks.NewTeamsSelector.cpp | 34 +++++++++++++++++++++ src/Ext/Scenario/Hooks.cpp | 32 ------------------- 3 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 src/Ext/Scenario/Hooks.NewTeamsSelector.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index cf9aedf0cc..a0815f6861 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -44,6 +44,7 @@ + diff --git a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp new file mode 100644 index 0000000000..2d31f85aa4 --- /dev/null +++ b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp @@ -0,0 +1,34 @@ +#include "Body.h" + +#include +#include + +DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) +{ + // For each house save a list with only AI Triggers that can be used + for (HouseClass* pHouse : *HouseClass::Array) + { + auto pHouseExt = HouseExt::ExtMap.Find(pHouse); + pHouseExt->AITriggers_ValidList.clear(); + int houseIdx = pHouse->ArrayIndex; + int sideIdx = pHouse->SideIndex + 1; + + for (int i = 0; i < AITriggerTypeClass::Array->Count; i++) + { + auto pTrigger = AITriggerTypeClass::Array->GetItem(i); + if (!pTrigger) + continue; + + int triggerHouse = pTrigger->HouseIndex; + int triggerSide = pTrigger->SideIndex; + + // The trigger must be compatible with the owner + if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + pHouseExt->AITriggers_ValidList.push_back(i); + } + + Debug::Log("House %d [%s] could use %d AI triggers in this map.\n", pHouse->ArrayIndex, pHouse->Type->ID, pHouseExt->AITriggers_ValidList.size()); + } + + return 0; +} diff --git a/src/Ext/Scenario/Hooks.cpp b/src/Ext/Scenario/Hooks.cpp index e9f174c2bf..cfcbf8d43e 100644 --- a/src/Ext/Scenario/Hooks.cpp +++ b/src/Ext/Scenario/Hooks.cpp @@ -1,10 +1,8 @@ #include "Body.h" -#include #include #include #include -#include DEFINE_HOOK(0x6870D7, ReadScenario_LoadingScreens, 0x5) { @@ -29,33 +27,3 @@ DEFINE_HOOK(0x6870D7, ReadScenario_LoadingScreens, 0x5) return SkipGameCode; } - -DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) -{ - // For each house save a list with only AI Triggers that can be used - for (HouseClass* pHouse : *HouseClass::Array) - { - auto pHouseExt = HouseExt::ExtMap.Find(pHouse); - pHouseExt->AITriggers_ValidList.clear(); - int houseIdx = pHouse->ArrayIndex; - int sideIdx = pHouse->SideIndex + 1; - - for (int i = 0; i < AITriggerTypeClass::Array->Count; i++) - { - auto pTrigger = AITriggerTypeClass::Array->GetItem(i); - if (!pTrigger) - continue; - - int triggerHouse = pTrigger->HouseIndex; - int triggerSide = pTrigger->SideIndex; - - // The trigger must be compatible with the owner - if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) - pHouseExt->AITriggers_ValidList.push_back(i); - } - - Debug::Log("House %d [%s] could use %d AI triggers in this map.\n", pHouse->ArrayIndex, pHouse->Type->ID, pHouseExt->AITriggers_ValidList.size()); - } - - return 0; -} From 2280ae28bb3aa5b671d674f89aa5aa5adb93e934 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 29 Apr 2024 09:53:32 +0200 Subject: [PATCH 26/34] Moved code into the new file --- src/Ext/Building/Body.cpp | 2 +- src/Ext/Team/Body.NewTeamsSelector.cpp | 58 ++++++++++++++++++++++++++ src/Ext/Team/Body.h | 58 -------------------------- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 043715f36e..ddd26dac18 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -329,7 +329,7 @@ bool BuildingExt::HandleInfiltrate(BuildingClass* pBuilding, HouseClass* pInfilt return true; } -// Assigns a secret production option to the AI building. +// Assigns a secret production option to the AI building (Ares doesn't handle the AI case). void BuildingExt::ExtData::UpdateSecretLabAI() { auto pThis = this->OwnerObject(); diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index 938bd2fbed..70a8ff6328 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -1,5 +1,63 @@ #include "Body.h" +enum teamCategory +{ + None = 0, // No category. Should be default value + Ground = 1, + Air = 2, + Naval = 3, + Unclassified = 4 +}; + +struct TriggerElementWeight +{ + double Weight = 0.0; + AITriggerTypeClass* Trigger = nullptr; + teamCategory Category = teamCategory::None; + + //need to define a == operator so it can be used in array classes + bool operator==(const TriggerElementWeight& other) const + { + return (Trigger == other.Trigger && Weight == other.Weight && Category == other.Category); + } + + //unequality + bool operator!=(const TriggerElementWeight& other) const + { + return (Trigger != other.Trigger || Weight != other.Weight || Category == other.Category); + } + + bool operator<(const TriggerElementWeight& other) const + { + return (Weight < other.Weight); + } + + bool operator<(const double other) const + { + return (Weight < other); + } + + bool operator>(const TriggerElementWeight& other) const + { + return (Weight > other.Weight); + } + + bool operator>(const double other) const + { + return (Weight > other); + } + + bool operator==(const double other) const + { + return (Weight == other); + } + + bool operator!=(const double other) const + { + return (Weight != other); + } +}; + DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { enum { UseOriginalSelector = 0x4F8A63, SkipCode = 0x4F8B08 }; diff --git a/src/Ext/Team/Body.h b/src/Ext/Team/Body.h index e1a40ef478..c342baf81b 100644 --- a/src/Ext/Team/Body.h +++ b/src/Ext/Team/Body.h @@ -12,64 +12,6 @@ #include #include -enum teamCategory -{ - None = 0, // No category. Should be default value - Ground = 1, - Air = 2, - Naval = 3, - Unclassified = 4 -}; - -struct TriggerElementWeight -{ - double Weight = 0.0; - AITriggerTypeClass* Trigger = nullptr; - teamCategory Category = teamCategory::None; - - //need to define a == operator so it can be used in array classes - bool operator==(const TriggerElementWeight& other) const - { - return (Trigger == other.Trigger && Weight == other.Weight && Category == other.Category); - } - - //unequality - bool operator!=(const TriggerElementWeight& other) const - { - return (Trigger != other.Trigger || Weight != other.Weight || Category == other.Category); - } - - bool operator<(const TriggerElementWeight& other) const - { - return (Weight < other.Weight); - } - - bool operator<(const double other) const - { - return (Weight < other); - } - - bool operator>(const TriggerElementWeight& other) const - { - return (Weight > other.Weight); - } - - bool operator>(const double other) const - { - return (Weight > other); - } - - bool operator==(const double other) const - { - return (Weight == other); - } - - bool operator!=(const double other) const - { - return (Weight != other); - } -}; - class TeamExt { public: From 673109778f5502cbc991fc19911181b75a4e2c40 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 1 May 2024 08:18:30 +0200 Subject: [PATCH 27/34] Fix rare crash and tweaks --- src/Ext/Team/Body.NewTeamsSelector.cpp | 83 +++++++++++--------------- src/Ext/Techno/Body.cpp | 17 ++++++ src/Ext/Techno/Body.h | 1 + 3 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index 70a8ff6328..da129c9a72 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -266,8 +266,10 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) std::map ownedRecruitables; std::map ownedBuildings; - for (auto pTechno : *TechnoClass::Array) + for (auto const pTechno : *TechnoClass::Array) { + if (!TechnoExt::IsValidTechno(pTechno)) continue; + if (pTechno->WhatAmI() == AbstractType::Building) { if (pTechno->Owner == pHouse) @@ -276,7 +278,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else { - auto pBuilding = static_cast(pTechno); + auto const pBuilding = static_cast(pTechno); if (pBuilding && pBuilding->Type->BridgeRepairHut) { CellStruct cell = pTechno->GetCell()->MapCoords; @@ -291,7 +293,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) continue; } - auto* pFoot = static_cast(pTechno); + auto const pFoot = static_cast(pTechno); if (!pFoot || !pTechno->IsAlive @@ -1010,10 +1012,9 @@ bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool alli { for (auto pObject : *TechnoClass::Array) { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && ((!allies && pObject->Owner == pHouse) || (allies && pHouse != pObject->Owner && pHouse->IsAlliedWith(pObject->Owner))) + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (((!allies && pObject->Owner == pHouse) || (allies && pHouse != pObject->Owner && pHouse->IsAlliedWith(pObject->Owner))) && !pObject->Owner->Type->MultiplayPassive && pObject->GetTechnoType() == pItem) { @@ -1058,14 +1059,13 @@ bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClas pEnemy = nullptr; // Count all objects of the list, like an OR operator - for (auto pItem : list) + for (auto const pItem : list) { - for (auto pObject : *TechnoClass::Array) + for (auto const pObject : *TechnoClass::Array) { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner != pHouse + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (pObject->Owner != pHouse && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) && !pObject->Owner->Type->MultiplayPassive && pObject->GetTechnoType() == pItem) @@ -1107,24 +1107,20 @@ bool TeamExt::NeutralOwns(AITriggerTypeClass* pThis, std::vectorGetItem(pHouse->Type->SideIndex)->Name, "Civilian") != 0) continue; // Count all objects of the list, like an OR operator - for (auto pItem : list) + for (auto const pItem : list) { - for (auto pObject : *TechnoClass::Array) + for (auto const pObject : *TechnoClass::Array) { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner == pHouse - && pObject->GetTechnoType() == pItem) - { + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (pObject->Owner == pHouse && pObject->GetTechnoType() == pItem) counter++; - } } } } @@ -1164,7 +1160,7 @@ bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::v return false; // Count all objects of the list, like an AND operator - for (auto pItem : list) + for (auto const pItem : list) { if (!result) break; @@ -1172,16 +1168,12 @@ bool TeamExt::HouseOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, std::v int counter = 0; result = true; - for (auto pObject : *TechnoClass::Array) + for (auto const pObject : *TechnoClass::Array) { - if (pObject && - pObject->IsAlive && - pObject->Health > 0 && - pObject->Owner == pHouse && - pObject->GetTechnoType() == pItem) - { + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (pObject->Owner == pHouse && pObject->GetTechnoType() == pItem) counter++; - } } switch (pThis->Conditions->ComparatorOperand) @@ -1223,7 +1215,7 @@ bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseC return false; // Count all objects of the list, like an AND operator - for (auto pItem : list) + for (auto const pItem : list) { if (!result) break; @@ -1231,12 +1223,11 @@ bool TeamExt::EnemyOwnsAll(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseC int counter = 0; result = true; - for (auto pObject : *TechnoClass::Array) + for (auto const pObject : *TechnoClass::Array) { - if (pObject - && pObject->IsAlive - && pObject->Health > 0 - && pObject->Owner != pHouse + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (pObject->Owner != pHouse && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) && !pObject->Owner->Type->MultiplayPassive && pObject->GetTechnoType() == pItem) @@ -1281,7 +1272,7 @@ bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vectorIsAlive && - pObject->Health > 0 && - pObject->Owner == pHouse && - pObject->GetTechnoType() == pItem) - { + if (!TechnoExt::IsValidTechno(pObject)) continue; + + if (pObject->Owner == pHouse && pObject->GetTechnoType() == pItem) counter++; - } } switch (pThis->Conditions->ComparatorOperand) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 3b90925c0d..842522b0d0 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -362,6 +363,22 @@ bool TechnoExt::IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource) return false; } +bool TechnoExt::IsValidTechno(TechnoClass* pTechno) +{ + if (!pTechno) + return false; + + bool isValid = !pTechno->Dirty + && ScriptExt::IsUnitAvailable(pTechno, true) + && pTechno->Owner + && (pTechno->WhatAmI() == AbstractType::Infantry + || pTechno->WhatAmI() == AbstractType::Unit + || pTechno->WhatAmI() == AbstractType::Building + || pTechno->WhatAmI() == AbstractType::Aircraft); + + return isValid; +} + // ============================= // load / save diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 5bebb0c86a..114fe5c007 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -144,6 +144,7 @@ class TechnoExt static bool ConvertToType(FootClass* pThis, TechnoTypeClass* toType); static bool CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue = false); static bool IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource); + static bool IsValidTechno(TechnoClass* pTechno); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); From 429d9572df4f594a73e33f74f1f4ab47f2c58fe7 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 11 May 2024 23:51:06 +0200 Subject: [PATCH 28/34] Added priority to defensive teams --- src/Ext/Team/Body.NewTeamsSelector.cpp | 30 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index da129c9a72..b465def51c 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -200,6 +200,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based auto houseDifficulty = pHouse->AIDifficulty; + int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); int maxBaseDefenseTeams = RulesClass::Instance->MaximumAIDefensiveTeams.GetItem((int)houseDifficulty); int activeDefenseTeamsCount = 0; int maxTeamsLimit = RulesClass::Instance->TotalAITeamCap.GetItem((int)houseDifficulty); @@ -222,16 +223,21 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) activeTeamsList.AddItem(pRunningTeam); - if (pRunningTeam->Type->IsBaseDefense && activeDefenseTeamsCount < maxBaseDefenseTeams) + if (pRunningTeam->Type->IsBaseDefense) activeDefenseTeamsCount++; } activeTeams = activeTeamsList.Count; - /*Debug::Log("=====================\n[%s] ACTIVE TEAMS: %d / %d\n", pHouse->Type->ID, activeTeams, maxTeamsLimit); + // We will use these values for discarding triggers + int defensiveTeamsLimit = RulesClass::Instance->UseMinDefenseRule ? minBaseDefenseTeams : maxBaseDefenseTeams; + bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; + bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < defensiveTeamsLimit ? false : true; + + /*Debug::Log("\n=====================\n[%s] ACTIVE TEAMS: %d / %d (of them, defensive teams: %d / %d)\n", pHouse->Type->ID, activeTeams, maxTeamsLimit, activeDefenseTeamsCount, defensiveTeamsLimit); for (auto team : activeTeamsList) { - Debug::Log("[%s](%d) : %s\n", team->Type->ID, team->TotalObjects, team->Type->Name); + Debug::Log("[%s](%d) : %s%s\n", team->Type->ID, team->TotalObjects, team->Type->Name, team->Type->IsBaseDefense ? " -> is DEFENDER team" : ""); Debug::Log(" IsMoving: %d, IsFullStrength: %d, IsUnderStrength: %d\n", team->IsMoving, team->IsFullStrength, team->IsUnderStrength); int i = 0; @@ -246,11 +252,15 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) i++; } } - Debug::Log("=====================\n");*/ + Debug::Log("\n=====================\n");*/ - // We will use these values for discarding triggers - bool hasReachedMaxTeamsLimit = activeTeams < maxTeamsLimit ? false : true; - bool hasReachedMaxDefensiveTeamsLimit = activeDefenseTeamsCount < maxBaseDefenseTeams ? false : true; + // Check if the next team must be a defensive team + bool onlyPickDefensiveTeams = false; + int defensiveDice = ScenarioClass::Instance->Random.RandomRanged(0, 99); + int defenseTeamSelectionThreshold = 50; + + if ((defensiveDice < defenseTeamSelectionThreshold) && !hasReachedMaxDefensiveTeamsLimit) + onlyPickDefensiveTeams = true; if (hasReachedMaxDefensiveTeamsLimit) Debug::Log("DEBUG: House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); @@ -321,6 +331,10 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!pTrigger) continue; + // Ignore offensive teams if the next trigger must be defensive + if (onlyPickDefensiveTeams && !pTrigger->IsForBaseDefense) + continue; + int triggerHouse = pTrigger->HouseIndex; int triggerSide = pTrigger->SideIndex; @@ -1294,7 +1308,7 @@ bool TeamExt::NeutralOwnsAll(AITriggerTypeClass* pThis, std::vectorOwner == pHouse && pObject->GetTechnoType() == pItem) + if (pObject->Owner == pHouse && pObject->GetTechnoType() == pItem) counter++; } From 6d473821208fdc4f8556890067b1bccdaccf3849 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 26 Oct 2024 22:49:47 +0200 Subject: [PATCH 29/34] Bug Fixes and some Trigger conditions implementations Also, now it won't ignore the AI difficulty and TechLevel. Fixes in Generic prerequisites --- src/Ext/House/Body.Prerequisites.cpp | 10 +- src/Ext/Rules/Body.cpp | 2 + src/Ext/Team/Body.NewTeamsSelector.cpp | 146 ++++++++++++++++--------- src/Ext/TechnoType/Body.cpp | 10 +- 4 files changed, 106 insertions(+), 62 deletions(-) diff --git a/src/Ext/House/Body.Prerequisites.cpp b/src/Ext/House/Body.Prerequisites.cpp index 7d190fe450..d0397999f9 100644 --- a/src/Ext/House/Body.Prerequisites.cpp +++ b/src/Ext/House/Body.Prerequisites.cpp @@ -205,8 +205,8 @@ bool HouseExt::HasGenericPrerequisite(int idx, std::map int HouseExt::FindGenericPrerequisite(const char* id) { - if (TechnoTypeClass::FindIndex(id) >= 0) - return 0; + if (BuildingTypeClass::FindIndex(id) >= 0) + return INT32_MAX; if (RulesExt::Global()->GenericPrerequisitesNames.Count == 0) RulesExt::FillDefaultPrerequisites(); // needed! @@ -215,10 +215,10 @@ int HouseExt::FindGenericPrerequisite(const char* id) for (auto str : RulesExt::Global()->GenericPrerequisitesNames) { if (_strcmpi(id, str) == 0) - return (-1 * i); + return i; - ++i; + --i; } - return 0; + return INT32_MAX; // Error } diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 47d24628ca..012a07c682 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -267,6 +267,8 @@ void RulesExt::FillDefaultPrerequisites() CCINIClass::INI_Rules; DynamicVectorClass empty; + RulesExt::Global()->GenericPrerequisitesNames.AddItem("-"); // Official index: 0 + RulesExt::Global()->GenericPrerequisites.AddItem(empty); RulesExt::Global()->GenericPrerequisitesNames.AddItem("POWER"); // Official index: -1 RulesExt::Global()->GenericPrerequisites.AddItem(RulesClass::Instance->PrerequisitePower); diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index b465def51c..92317b1197 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -166,22 +166,22 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (percentageUnclassifiedTriggers > 0.0 && categoryDice <= unclassifiedValue) { validCategory = teamCategory::Unclassified; - Debug::Log("New AI team category selection: dice %d <= %d (MIXED)\n", categoryDice, unclassifiedValue); + //Debug::Log("New AI team category selection: dice %d <= %d (MIXED)\n", categoryDice, unclassifiedValue); } else if (percentageGroundTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue)) { validCategory = teamCategory::Ground; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + GROUND: %d%%)\n", categoryDice, (unclassifiedValue + groundValue), unclassifiedValue, groundValue); + //Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + GROUND: %d%%)\n", categoryDice, (unclassifiedValue + groundValue), unclassifiedValue, groundValue); } else if (percentageAirTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue)) { validCategory = teamCategory::Air; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + AIR: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue), unclassifiedValue, groundValue, airValue); + //Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + AIR: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue), unclassifiedValue, groundValue, airValue); } else if (percentageNavalTriggers > 0.0 && categoryDice <= (unclassifiedValue + groundValue + airValue + navalValue)) { validCategory = teamCategory::Naval; - Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + air: %d%% + NAVAL: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue + navalValue), unclassifiedValue, groundValue, airValue, navalValue); + //Debug::Log("New AI team category selection: dice %d <= %d (mixed: %d%% + ground: %d%% + air: %d%% + NAVAL: %d%%)\n", categoryDice, (unclassifiedValue + groundValue + airValue + navalValue), unclassifiedValue, groundValue, airValue, navalValue); } else { @@ -197,7 +197,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based - int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based + int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based -> unused variable!! auto houseDifficulty = pHouse->AIDifficulty; int minBaseDefenseTeams = RulesClass::Instance->MinimumAIDefensiveTeams.GetItem((int)houseDifficulty); @@ -263,11 +263,11 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) onlyPickDefensiveTeams = true; if (hasReachedMaxDefensiveTeamsLimit) - Debug::Log("DEBUG: House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AITeamsSelector - House [%s] (idx: %d) reached the MaximumAIDefensiveTeams value!\n", pHouse->Type->ID, pHouse->ArrayIndex); if (hasReachedMaxTeamsLimit) { - Debug::Log("DEBUG: House [%s] (idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AITeamsSelector - House [%s] (idx: %d) reached the TotalAITeamCap value!\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } @@ -341,6 +341,18 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) // Ignore the deactivated triggers if (pTrigger->IsEnabled) { + //pTrigger->OwnerHouseType; + if (pTrigger->TechLevel > pHouse->TechLevel) + continue; + + // ignore it if isn't set for the house AI difficulty + if ((int)houseDifficulty == 0 && !pTrigger->Enabled_Hard + || (int)houseDifficulty == 1 && !pTrigger->Enabled_Normal + || (int)houseDifficulty == 2 && !pTrigger->Enabled_Easy) + { + continue; + } + // The trigger must be compatible with the owner if ((triggerHouse == -1 || houseTypeIdx == triggerHouse) && (triggerSide == 0 || sideTypeIdx == triggerSide)) { @@ -370,6 +382,30 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) list.push_back(pTrigger->ConditionObject); bool isConditionMet = TeamExt::HouseOwns(pTrigger, pHouse, false, list); + if (!isConditionMet) + continue; + }// TO-DO: Case 2 & 3: https://modenc.renegadeprojects.com/AITriggerTypes + else if ((int)pTrigger->ConditionType == 4) + { + // Simulate case 5: "Enemy house economy threshold?" + bool isConditionMet = pTrigger->HouseCredits(nullptr, targetHouse); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 5) + { + // Simulate case 5: "Iron Courtain is charged?" + bool isConditionMet = pTrigger->IronCurtainCharged(pHouse, nullptr); + + if (!isConditionMet) + continue; + } + else if ((int)pTrigger->ConditionType == 6) + { + // Simulate case 6: "Chronosphere is charged?" + bool isConditionMet = pTrigger->ChronoSphereCharged(pHouse, nullptr); + if (!isConditionMet) continue; } @@ -401,7 +437,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 9) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 9: Like in case 0 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the enemy. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -414,7 +450,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 10) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 10: Like in case 1 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the house. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -427,7 +463,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 11) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 11: Like in case 7 but instead of 1 unit for comparisons there is a full list from [AITargetTypes] owned by the Civilians. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -440,7 +476,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 12) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 12: Like in case 0 & 9 but instead of a specific enemy this checks in all enemies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -453,7 +489,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 13) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 13: Like in case 1 & 10 but instead checking the house now checks the allies. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -466,7 +502,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 14) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 14: Like in case 9 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -479,7 +515,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 15) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 15: Like in case 10 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -492,7 +528,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 16) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 16: Like in case 11 but instead of meet any comparison now is required all. // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -505,7 +541,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } else if ((int)pTrigger->ConditionType == 17) { - if (pTrigger->Conditions[3].ComparatorOperand < RulesExt::Global()->AITargetTypesLists.size()) + if (pTrigger->Conditions[3].ComparatorOperand < (int)RulesExt::Global()->AITargetTypesLists.size()) { // New case 17: Like in case 14 but instead of meet any comparison now is required all. Check all enemies // Caution: Little Endian hexadecimal value stored here: 00000000000000000000000000000000000000000000000000000000AABBCCDD; examples: 255 is 0xFF (in AA) and 256 is 0x0001 (in AABB) @@ -646,12 +682,12 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { + if (!entry.Type) + continue; + // Check if each unit in the taskforce meets the structure prerequisites if (entry.Amount > 0) { - if (!entry.Type) - continue; - TechnoTypeClass* object = entry.Type; bool canBeBuilt = HouseExt::PrerequisitesMet(pHouse, object, ownedBuildings, false); @@ -678,10 +714,15 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) for (auto entry : pTriggerTeam1Type->TaskForce->Entries) { + if (!entry.Type) + continue; + // Check if each unit in the taskforce has the available recruitable units in the map if (allObjectsCanBeBuiltOrRecruited && entry.Type && entry.Amount > 0) { - if (ownedRecruitables.count(entry.Type) < entry.Amount) + int recruits = ownedRecruitables.count(entry.Type) > 0 ? ownedRecruitables[entry.Type] : 0; + + if (recruits < entry.Amount) { allObjectsCanBeBuiltOrRecruited = false; break; @@ -791,30 +832,30 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) switch (validCategory) { case teamCategory::Ground: - Debug::Log("DEBUG: This time only will be picked GROUND teams.\n"); + Debug::Log("AITeamsSelector - This time only will be picked GROUND teams.\n"); break; case teamCategory::Unclassified: - Debug::Log("DEBUG: This time only will be picked MIXED teams.\n"); + Debug::Log("AITeamsSelector - This time only will be picked MIXED teams.\n"); break; case teamCategory::Naval: - Debug::Log("DEBUG: This time only will be picked NAVAL teams.\n"); + Debug::Log("AITeamsSelector - This time only will be picked NAVAL teams.\n"); break; case teamCategory::Air: - Debug::Log("DEBUG: This time only will be picked AIR teams.\n"); + Debug::Log("AITeamsSelector - This time only will be picked AIR teams.\n"); break; default: - Debug::Log("DEBUG: This time teams categories are DISABLED.\n"); + Debug::Log("AITeamsSelector - This time teams categories are DISABLED.\n"); break; } } if (validTriggerCandidates.Count == 0) { - Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers for now. A new attempt will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AITeamsSelector - [%s] (idx: %d) No valid triggers for now. A new attempt will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } @@ -823,12 +864,12 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) || (validCategory == teamCategory::Air && totalAirCategoryTriggers == 0) || (validCategory == teamCategory::Naval && totalNavalCategoryTriggers == 0)) { - Debug::Log("DEBUG: [%s] (idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AITeamsSelector - [%s] (idx: %d) No valid triggers of this category. A new attempt should be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); if (!isFallbackEnabled) return SkipCode; - Debug::Log("... but fallback mode is enabled so now will be checked all available triggers.\n"); + Debug::Log("... but FALLBACK MODE is enabled so now will be checked all available triggers.\n"); validCategory = teamCategory::None; } @@ -841,13 +882,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) { case teamCategory::None: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeight) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); + Debug::Log("AITeamsSelector - Weight Dice: %f\n", weightDice); // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); + /*Debug::Log("DEBUG: Candidate AI triggers list:\n"); for (TriggerElementWeight element : validTriggerCandidates) { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + Debug::Log("Selection weight: %f, [%s][%s](CW: %f): %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Weight_Current, element.Trigger->Team1->Name); }*/ for (auto element : validTriggerCandidates) @@ -864,13 +905,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Ground: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightGroundOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); + Debug::Log("AITeamsSelector - Weight Dice: %f\n", weightDice); // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); + /*Debug::Log("DEBUG: Candidate AI triggers list:\n"); for (TriggerElementWeight element : validTriggerCandidatesGroundOnly) { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + Debug::Log("Selection weight: %f, [%s][%s](CW: %f): %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Weight_Current, element.Trigger->Team1->Name); }*/ for (auto element : validTriggerCandidatesGroundOnly) @@ -887,13 +928,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Unclassified: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightUnclassifiedOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); + Debug::Log("AITeamsSelector - Weight Dice: %f\n", weightDice); // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); + /*Debug::Log("DEBUG: Candidate AI triggers list:\n"); for (TriggerElementWeight element : validTriggerCandidatesUnclassifiedOnly) { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + Debug::Log("Selection weight: %f, [%s][%s](CW: %f): %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Weight_Current, element.Trigger->Team1->Name); }*/ for (auto element : validTriggerCandidatesUnclassifiedOnly) @@ -910,13 +951,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Naval: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightNavalOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); + Debug::Log("AITeamsSelector - Weight Dice: %f\n", weightDice); // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); + /*Debug::Log("DEBUG: Candidate AI triggers list:\n"); for (TriggerElementWeight element : validTriggerCandidatesNavalOnly) { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + Debug::Log("Selection weight: %f, [%s][%s](CW: %f): %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Weight_Current, element.Trigger->Team1->Name); }*/ for (auto element : validTriggerCandidatesNavalOnly) @@ -933,13 +974,13 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) case teamCategory::Air: weightDice = ScenarioClass::Instance->Random.RandomRanged(0, (int)totalWeightAirOnly) * 1.0; - /*Debug::Log("Weight Dice: %f\n", weightDice); + Debug::Log("AITeamsSelector - Weight Dice: %f\n", weightDice); // Debug - Debug::Log("DEBUG: Candidate AI triggers list:\n"); + /*Debug::Log("DEBUG: Candidate AI triggers list:\n"); for (TriggerElementWeight element : validTriggerCandidatesAirOnly) { - Debug::Log("Weight: %f, [%s][%s]: %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Team1->Name); + Debug::Log("Selection weight: %f, [%s][%s](CW: %f): %s\n", element.Weight, element.Trigger->ID, element.Trigger->Team1->ID, element.Trigger->Weight_Current, element.Trigger->Team1->Name); }*/ for (auto element : validTriggerCandidatesAirOnly) @@ -960,7 +1001,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (!selectedTrigger) { - Debug::Log("AI Team Selector: House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); + Debug::Log("AITeamsSelector - House [%s] (idx: %d) failed to select Trigger. A new attempt Will be done later...\n", pHouse->Type->ID, pHouse->ArrayIndex); return SkipCode; } @@ -972,7 +1013,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) } // We have a winner trigger here - Debug::Log("AI Team Selector: House [%s] (idx: %d) selected trigger [%s].\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID); + Debug::Log("AITeamsSelector - House [%s] (idx: %d) selected trigger [%s]: %s.\n", pHouse->Type->ID, pHouse->ArrayIndex, selectedTrigger->ID, selectedTrigger->Team1->Name); // Team 1 creation auto pTriggerTeam1Type = selectedTrigger->Team1; @@ -1064,13 +1105,13 @@ bool TeamExt::HouseOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, bool alli return result; } -bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pEnemy, bool onlySelectedEnemy, std::vector list) +bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClass* pSpecificEnemy, bool onlySelectedEnemy, std::vector list) { bool result = false; int counter = 0; - if (pEnemy && pHouse->IsAlliedWith(pEnemy) && !onlySelectedEnemy) - pEnemy = nullptr; + pSpecificEnemy = pSpecificEnemy && pHouse->IsAlliedWith(pSpecificEnemy) ? nullptr : pSpecificEnemy; + pSpecificEnemy = onlySelectedEnemy ? pSpecificEnemy : nullptr; // Count all objects of the list, like an OR operator for (auto const pItem : list) @@ -1079,10 +1120,11 @@ bool TeamExt::EnemyOwns(AITriggerTypeClass* pThis, HouseClass* pHouse, HouseClas { if (!TechnoExt::IsValidTechno(pObject)) continue; - if (pObject->Owner != pHouse - && (!pEnemy || (pEnemy && !pHouse->IsAlliedWith(pEnemy))) - && !pObject->Owner->Type->MultiplayPassive - && pObject->GetTechnoType() == pItem) + const auto pItemType = pObject->GetTechnoType(); + if (pItemType == pItem + && pObject->Owner != pHouse + && (!pSpecificEnemy && !pHouse->IsAlliedWith(pObject->Owner) || (pSpecificEnemy && pSpecificEnemy == pObject->Owner)) + && !pObject->Owner->Type->MultiplayPassive) { counter++; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 357b8880f1..2b4d028ae0 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -469,7 +469,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ConsideredSecretLabTech.Read(exINI, pSection, "ConsideredSecretLabTech"); // Secret.RequiredHouses contains a list of HouseTypeClass indexes - this->Secret_RequiredHouses.clear(); + //this->Secret_RequiredHouses.clear(); const char* key = "SecretLab.RequiredHouses"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -483,7 +483,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Secret.ForbiddenHouses contains a list of HouseTypeClass indexes - this->Secret_ForbiddenHouses.clear(); + //this->Secret_ForbiddenHouses.clear(); key = "SecretLab.ForbiddenHouses"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -513,7 +513,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) // Prerequisite with Generic Prerequistes support. // Note: I have no idea of what could happen in all the game engine logics if I push the negative indexes of the Ares generic prerequisites directly into the original Prerequisite tag... for that reason this tag is duplicated for working with it - this->Prerequisite.clear(); + //this->Prerequisite.clear(); key = "Prerequisite"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -536,7 +536,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite.Negative with Generic Prerequistes support - this->Prerequisite_Negative.clear(); + //this->Prerequisite_Negative.clear(); key = "Prerequisite.Negative"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); @@ -559,7 +559,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = nullptr; // Prerequisite.ListX with Generic Prerequistes support - this->Prerequisite_ListVector.clear(); + //this->Prerequisite_ListVector.clear(); this->Prerequisite_Lists.Read(exINI, pSection, "Prerequisite.Lists"); if (Prerequisite_Lists.Get() > 0) From 9f2a84ebd1c2968446114845377a541cde325e49 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 18 Feb 2025 13:45:59 +0100 Subject: [PATCH 30/34] Fixes & tweaks --- src/Ext/Scenario/Hooks.NewTeamsSelector.cpp | 4 +++- src/Ext/Team/Body.NewTeamsSelector.cpp | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp index 2d31f85aa4..e9782b2758 100644 --- a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp +++ b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp @@ -5,6 +5,8 @@ DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) { + bool ignoreGlobalAITriggers = ScenarioClass::Instance->IgnoreGlobalAITriggers; + // For each house save a list with only AI Triggers that can be used for (HouseClass* pHouse : *HouseClass::Array) { @@ -16,7 +18,7 @@ DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) for (int i = 0; i < AITriggerTypeClass::Array->Count; i++) { auto pTrigger = AITriggerTypeClass::Array->GetItem(i); - if (!pTrigger) + if (!pTrigger || ignoreGlobalAITriggers == pTrigger->IsGlobal || !pTrigger->Team1) continue; int triggerHouse = pTrigger->HouseIndex; diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index 92317b1197..91488be316 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -66,7 +66,9 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) bool houseIsHuman = pHouse->IsHumanPlayer; - if (SessionClass::IsCampaign()) + bool isCampaign = SessionClass::IsCampaign(); + + if (isCampaign) houseIsHuman = pHouse->IsHumanPlayer || pHouse->IsInPlayerControl; if (houseIsHuman || pHouse->Type->MultiplayPassive) @@ -259,7 +261,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int defensiveDice = ScenarioClass::Instance->Random.RandomRanged(0, 99); int defenseTeamSelectionThreshold = 50; - if ((defensiveDice < defenseTeamSelectionThreshold) && !hasReachedMaxDefensiveTeamsLimit) + if ((defensiveDice < defenseTeamSelectionThreshold) && !hasReachedMaxDefensiveTeamsLimit && !isCampaign) onlyPickDefensiveTeams = true; if (hasReachedMaxDefensiveTeamsLimit) @@ -324,11 +326,17 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) targetHouse = HouseClass::Array->GetItem(pHouse->EnemyHouseIndex); bool onlyCheckImportantTriggers = false; + bool ignoreGlobalAITriggers = ScenarioClass::Instance->IgnoreGlobalAITriggers; + + const auto pHouseExt = HouseExt::ExtMap.Find(pHouse); // Gather all the trigger candidates into one place for posterior fast calculations - for (auto const pTrigger : *AITriggerTypeClass::Array) + //for (auto const pTrigger : *AITriggerTypeClass::Array) + for (int triggerIdx : pHouseExt->AITriggers_ValidList) { - if (!pTrigger) + const auto pTrigger = AITriggerTypeClass::Array->GetItem(triggerIdx); + + if (!pTrigger || ignoreGlobalAITriggers == pTrigger->IsGlobal || !pTrigger->Team1) continue; // Ignore offensive teams if the next trigger must be defensive @@ -342,7 +350,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) if (pTrigger->IsEnabled) { //pTrigger->OwnerHouseType; - if (pTrigger->TechLevel > pHouse->TechLevel) + if (pTrigger->Team1->TechLevel > pHouse->TechLevel) continue; // ignore it if isn't set for the house AI difficulty From 15c5e979f91f95ecb25e946798854e863c09f4fa Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 18 Feb 2025 19:21:04 +0100 Subject: [PATCH 31/34] Fix --- src/Ext/TechnoType/Body.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 1b2e4ca128..39c3037085 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -627,6 +627,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) const char* key = "SecretLab.RequiredHouses"; char* context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + this->Secret_RequiredHouses.clear(); for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { @@ -641,6 +642,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = "SecretLab.ForbiddenHouses"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + this->Secret_ForbiddenHouses.clear(); for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { @@ -655,6 +657,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = "Prerequisite.RequiredTheaters"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + this->Prerequisite_RequiredTheaters.clear(); for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { @@ -671,6 +674,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = "Prerequisite"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + this->Prerequisite.clear(); for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { @@ -694,6 +698,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) key = "Prerequisite.Negative"; context = nullptr; pINI->ReadString(pSection, key, "", Phobos::readBuffer); + this->Prerequisite_Negative.clear(); for (char* cur = strtok_s(Phobos::readBuffer, Phobos::readDelims, &context); cur; cur = strtok_s(nullptr, Phobos::readDelims, &context)) { @@ -715,6 +720,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) // Prerequisite.ListX with Generic Prerequistes support //this->Prerequisite_ListVector.clear(); this->Prerequisite_Lists.Read(exINI, pSection, "Prerequisite.Lists"); + this->Prerequisite_ListVector.clear(); if (Prerequisite_Lists.Get() > 0) { From cd8a81f57fa72d6315b9f00a4e570fb202f73d05 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Thu, 20 Feb 2025 12:51:44 +0100 Subject: [PATCH 32/34] Tweak --- src/Ext/Techno/Body.cpp | 10 ++++++++-- src/Ext/Techno/Body.h | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 2994386e02..3d63e6f695 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -475,13 +475,19 @@ int TechnoExt::ExtData::GetAttachedEffectCumulativeCount(AttachEffectTypeClass* return foundCount; } -bool TechnoExt::IsValidTechno(TechnoClass* pTechno) +bool TechnoExt::IsValidTechno(AbstractClass* pObject, bool checkIfInTransportOrAbsorbed) +{ + const auto pTechno = abstract_cast(pObject); + return pTechno ? IsValidTechno(pTechno, checkIfInTransportOrAbsorbed) : false; +} + +bool TechnoExt::IsValidTechno(TechnoClass* pTechno, bool checkIfInTransportOrAbsorbed) { if (!pTechno) return false; bool isValid = !pTechno->Dirty - && ScriptExt::IsUnitAvailable(pTechno, true) + && ScriptExt::IsUnitAvailable(pTechno, checkIfInTransportOrAbsorbed) && pTechno->Owner && (pTechno->WhatAmI() == AbstractType::Infantry || pTechno->WhatAmI() == AbstractType::Unit diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index e83213c233..490728b2ba 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -176,7 +176,8 @@ class TechnoExt static Point2D GetBuildingSelectBracketPosition(TechnoClass* pThis, BuildingSelectBracketPosition bracketPosition); static void ProcessDigitalDisplays(TechnoClass* pThis); static void GetValuesForDisplay(TechnoClass* pThis, DisplayInfoType infoType, int& value, int& maxValue); - static bool IsValidTechno(TechnoClass* pTechno); + static bool IsValidTechno(TechnoClass* pTechno, bool checkIfInTransportOrAbsorbed = true); + static bool IsValidTechno(AbstractClass* pObject, bool checkIfInTransportOrAbsorbed = true); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); From 3a6f2e15ac182953cea6efec21f61cd6a576bd68 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sun, 10 Aug 2025 17:33:09 +0200 Subject: [PATCH 33/34] Fix AI in single player campaigns --- src/Ext/Scenario/Hooks.NewTeamsSelector.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp index 24dbda755a..403a8edb82 100644 --- a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp +++ b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp @@ -12,8 +12,15 @@ DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) { auto pHouseExt = HouseExt::ExtMap.Find(pHouse); pHouseExt->AITriggers_ValidList.clear(); + + int parentCountryTypeIdx = pHouse->Type->FindParentCountryIndex(); // ParentCountry can change the House in a SP map + int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based int houseIdx = pHouse->ArrayIndex; - int sideIdx = pHouse->SideIndex + 1; + + int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; + int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based + int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based -> unused variable!! + for (int i = 0; i < AITriggerTypeClass::Array.Count; i++) { @@ -25,7 +32,8 @@ DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) int triggerSide = pTrigger->SideIndex; // The trigger must be compatible with the owner - if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + //if ((triggerHouse == -1 || houseIdx == triggerHouse) && (triggerSide == 0 || sideIdx == triggerSide)) + if ((triggerHouse == -1 || houseTypeIdx == triggerHouse) && (triggerSide == 0 || sideTypeIdx == triggerSide)) pHouseExt->AITriggers_ValidList.push_back(i); } From b497b0a743a5b6ce3dc38a2efe201774aff419d5 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Mon, 11 Aug 2025 12:01:53 +0200 Subject: [PATCH 34/34] Fix crash --- src/Ext/Scenario/Hooks.NewTeamsSelector.cpp | 2 +- src/Ext/Team/Body.NewTeamsSelector.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp index 403a8edb82..6e947bfbba 100644 --- a/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp +++ b/src/Ext/Scenario/Hooks.NewTeamsSelector.cpp @@ -17,7 +17,7 @@ DEFINE_HOOK(0x687C9B, ReadScenarioINI_AITeamSelector_PreloadValidTriggers, 0x7) int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based int houseIdx = pHouse->ArrayIndex; - int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; + int parentCountrySideTypeIdx = parentCountryTypeIdx >= 0 ? pHouse->Type->FindParentCountry()->SideIndex : pHouse->Type->SideIndex; int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based -> unused variable!! diff --git a/src/Ext/Team/Body.NewTeamsSelector.cpp b/src/Ext/Team/Body.NewTeamsSelector.cpp index 64ca8c54f9..dd27d9bd36 100644 --- a/src/Ext/Team/Body.NewTeamsSelector.cpp +++ b/src/Ext/Team/Body.NewTeamsSelector.cpp @@ -197,7 +197,7 @@ DEFINE_HOOK(0x4F8A27, TeamTypeClass_SuggestedNewTeam_NewTeamsSelector, 0x5) int houseTypeIdx = parentCountryTypeIdx >= 0 ? parentCountryTypeIdx : pHouse->Type->ArrayIndex; // Indexes in AITriggers section are 1-based int houseIdx = pHouse->ArrayIndex; - int parentCountrySideTypeIdx = pHouse->Type->FindParentCountry()->SideIndex; + int parentCountrySideTypeIdx = parentCountryTypeIdx >= 0 ? pHouse->Type->FindParentCountry()->SideIndex : pHouse->Type->SideIndex; int sideTypeIdx = parentCountrySideTypeIdx >= 0 ? parentCountrySideTypeIdx + 1 : pHouse->Type->SideIndex + 1; // Side indexes in AITriggers section are 1-based int sideIdx = pHouse->SideIndex + 1; // Side indexes in AITriggers section are 1-based -> unused variable!!