From 873c34afdd5adf7d2955c9b6d6483687126f0b80 Mon Sep 17 00:00:00 2001 From: Gynt Date: Wed, 14 Oct 2026 09:41:38 +0200 Subject: [PATCH 01/23] reimplement: AICState methods that are less than 100% --- .../AI/AICState/addEngineersToTribe.cpp | 33 ++ .../AI/AICState/addRallyPointForTribe.cpp | 57 ++++ src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp | 27 ++ .../addUnitToSmallestBehaviourTypeTribe.cpp | 82 +++++ .../AI/AICState/addUnitsToTribe166.cpp | 35 +++ .../AICState/aiAddUnitToMoatDiggerTribe.cpp | 31 ++ .../AI/AICState/aiAssignMoatDiggers.cpp | 60 ++++ .../AICState/aiAssignUnitToDefensiveTribe.cpp | 26 ++ src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp | 28 ++ .../aiCommandTribe12AggressiveApproach.cpp | 58 ++++ .../AICState/aiCommandWallAssaultTribes.cpp | 35 +++ .../AI/AICState/aiCreateSiegeUnits.cpp | 234 +++++++++++++++ .../AI/AICState/aiGiveOuterPatrolCommand.cpp | 88 ++++++ .../AI/AICState/aiGiveRaidInstructions.cpp | 147 +++++++++ .../AICState/aiReassignTunnelersToTribe.cpp | 35 +++ .../AICState/aiRecruitEngineerForOilDuty.cpp | 71 +++++ .../AI/AICState/aiRecruitSortieMeleeUnits.cpp | 69 +++++ .../AICState/aiRecruitSortieRangedUnits.cpp | 70 +++++ src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 201 +++++++++++++ .../aiRegisterTribeAndAssignTarget.cpp | 37 +++ src/OpenSHC/AI/AICState/aiRetreatUnits.cpp | 46 +++ ...iShouldAttackOrWaitForTeamCoordination.cpp | 81 +++++ src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp | 29 ++ .../AI/AICState/aiShouldBuildIronMine.cpp | 32 ++ .../AI/AICState/aiShouldBuildPitchrig.cpp | 32 ++ .../AI/AICState/aiShouldBuildQuarry.cpp | 32 ++ .../AI/AICState/aiShouldBuildWoodcutter.cpp | 41 +++ src/OpenSHC/AI/AICState/buyGoods.cpp | 7 +- src/OpenSHC/AI/AICState/buyRequiredGoods.cpp | 58 ++++ .../canNavigateUnitsFromTileToTargetTile.cpp | 25 ++ ...mBuildingIDFromPlayersArrayOfBuildings.cpp | 45 +++ .../clearPlayerDataInformationChunk.cpp | 22 ++ .../clearRallyFlagOnAllAIAttackTribes.cpp | 37 +++ .../AI/AICState/commandFourTribesToMove.cpp | 40 +++ .../AICState/computeAttackPossibilities.cpp | 38 +++ .../AICState/computeEnemyKeepApproachTile.cpp | 85 ++++++ .../AI/AICState/createTribeForUnitType.cpp | 26 ++ .../AICState/decideOnTribeAttackLocation.cpp | 154 ++++++++++ src/OpenSHC/AI/AICState/destroyHouse.cpp | 36 +++ src/OpenSHC/AI/AICState/digNearestMoat.cpp | 54 ++++ .../AI/AICState/disbandAIPlayerSiegeUnits.cpp | 43 +++ .../AI/AICState/getDefensiveTribeForUnit.cpp | 53 ++++ .../giveMoveCommandToAttackTribes.cpp | 50 ++++ .../AI/AICState/giveSomeRaidCommand.cpp | 45 +++ ...BuyWhenLowOnResourceAndSnoozeBuildings.cpp | 226 ++++++++++++++ .../AI/AICState/playNervous2BikFromPlayer.cpp | 35 +++ .../recruitHarrassingSiegeEngines.cpp | 162 ++++++++++ .../AI/AICState/renderDebugDataAiInfo.cpp | 144 +++++++++ .../AI/AICState/selectAttackTarget.cpp | 143 +++++++++ src/OpenSHC/AI/AICState/sellExcessGoods.cpp | 113 +++++++ ...sendWallPatrolUnitTribesToAIVLocations.cpp | 94 ++++++ .../setCurrentAttackRaidParameter.cpp | 40 +++ .../AICState/setTriggerForAITauntResponse.cpp | 17 ++ .../AI/AICState/smallestTribeOfUnitType.cpp | 59 ++++ .../AICState/syncBehaviourType7UnitTribes.cpp | 35 +++ .../AI/AICState/updateAIPlayerState.cpp | 283 ++++++++++++++++++ .../AI/AICState/updateAIStrengthState.cpp | 214 +++++++++++++ 57 files changed, 4097 insertions(+), 3 deletions(-) create mode 100644 src/OpenSHC/AI/AICState/addEngineersToTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp create mode 100644 src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp create mode 100644 src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRetreatUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp create mode 100644 src/OpenSHC/AI/AICState/buyRequiredGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp create mode 100644 src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp create mode 100644 src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp create mode 100644 src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp create mode 100644 src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp create mode 100644 src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp create mode 100644 src/OpenSHC/AI/AICState/createTribeForUnitType.cpp create mode 100644 src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp create mode 100644 src/OpenSHC/AI/AICState/destroyHouse.cpp create mode 100644 src/OpenSHC/AI/AICState/digNearestMoat.cpp create mode 100644 src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp create mode 100644 src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp create mode 100644 src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp create mode 100644 src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp create mode 100644 src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp create mode 100644 src/OpenSHC/AI/AICState/selectAttackTarget.cpp create mode 100644 src/OpenSHC/AI/AICState/sellExcessGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp create mode 100644 src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp create mode 100644 src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp create mode 100644 src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp create mode 100644 src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/updateAIPlayerState.cpp create mode 100644 src/OpenSHC/AI/AICState/updateAIStrengthState.cpp diff --git a/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp new file mode 100644 index 000000000..48bc61b6a --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp @@ -0,0 +1,33 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4040 + void AICState::addEngineersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 10) + continue; + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + continue; + + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 10); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp new file mode 100644 index 000000000..5a8527736 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp @@ -0,0 +1,57 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFA10 + void AICState::addRallyPointForTribe(int tribeID) + { + DAT_TribesState::instance.tribes[tribeID].rallyPointCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getTeamsDifferent, DAT_GameState::ptr)( + DAT_TribesState::instance.tribes[tribeID].owner, DAT_UnitsState::instance.units[unitID].owner) + != FALSE) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeNextRallyPointDestination, + DAT_PathFindingState::ptr)( + -1, DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y); + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][0] + = DAT_PathFindingState::instance.ALG_ResultX; + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][1] + = DAT_PathFindingState::instance.ALG_ResultY; + DAT_TribesState::instance.tribes[tribeID].rallyPointCount++; + if (DAT_TribesState::instance.tribes[tribeID].rallyPointCount >= 9) + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex < 0) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + >= DAT_TribesState::instance.tribes[tribeID].rallyPointCount) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp new file mode 100644 index 000000000..0cf637150 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D27E0 + void AICState::addUnitToItsTribe(int unitID, int aiUnitBehaviourType) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = aiUnitBehaviourType; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::addUnitToSmallestBehaviourTypeTribe, this)( + owner, unitID, aiUnitBehaviourType); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp new file mode 100644 index 000000000..5d3e5ddf3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -0,0 +1,82 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCD20 + int AICState::addUnitToSmallestBehaviourTypeTribe( + undefined4 playerID, undefined4 unitID, undefined4 aiUnitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + + int aicIndex = aiType - 1; + int groupsCount; + switch (DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]) { + case 15: + groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; + break; + case 186: + groupsCount = this->aics[aicIndex].AttUnitBackupGroupsCount; + break; + case 190: + groupsCount = this->aics[aicIndex].AttUnitSiegeDefGroupsCount; + break; + case 192: + groupsCount = this->aics[aicIndex].AttMainGroupsCount; + break; + default: + groupsCount = 1; + break; + } + + int selectedTribeID = 0; + int selectedSlot = 0; + if (groupsCount <= 1) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]) + return tribeID; + selectedTribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + } else { + int smallestSize = 1000; + for (int slot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; slot + < DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10] + groupsCount; + slot++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[slot]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[slot]) { + selectedTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = slot; + break; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedSlot = slot; + } + } + } + if (selectedTribeID <= 0) + return 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedSlot] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedSlot] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp new file mode 100644 index 000000000..5e0cbafd8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC720 + void AICState::addUnitsToTribe166(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 6) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp new file mode 100644 index 000000000..4857156f9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp @@ -0,0 +1,31 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC840 + void AICState::aiAddUnitToMoatDiggerTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 5; + int tribeID = DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10]) { + tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(owner); + DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10] = tribeID; + DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10] + = DAT_TribesState::instance.tribes[tribeID].uid; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp new file mode 100644 index 000000000..6821d3933 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp @@ -0,0 +1,60 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3F20 + void AICState::aiAssignMoatDiggers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType0 <= 0) + return; + + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].isSelectable_OR_matchTime == 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_LORD + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ENGINEER + || DAT_UnitsState::instance.units[unitID].unitType == UT_TUNNELER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TREBUCHET + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_MANGONEL + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_SHIELD + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BALLISTA + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_FBALLISTA) + continue; + + if (this->aics[aicIndex].DefDiggingUnitMax == 0 + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ARCHER + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_XBOW + || DAT_UnitsState::instance.units[unitID].someUnitStat4 == 0) + MACRO_CALL_MEMBER(AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp new file mode 100644 index 000000000..bd7a41dec --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp @@ -0,0 +1,26 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2790 + void AICState::aiAssignUnitToDefensiveTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 2; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getDefensiveTribeForUnit, this)(owner, unitID); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp new file mode 100644 index 000000000..2d81632a7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp @@ -0,0 +1,28 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D48F0 + void AICState::aiBuyAndSellGoods(int playerID) + { + int marketID = DAT_GameState::instance.playerDataArray[playerID].marketplace.id; + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 0; + if (marketID != 0) + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 1; + if (DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk == 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].willBuy + = DAT_GameState::instance.playerDataArray[playerID].willBuy ^ TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].willBuy == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellExcessGoods, this)(playerID); + return; + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyRequiredGoods, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp new file mode 100644 index 000000000..818b68d3c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF2E0 + void AICState::aiCommandTribe12AggressiveApproach(int playerID) + { + int tribeIndex = 12; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, -1, -1, 10000, FALSE); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 200) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp new file mode 100644 index 000000000..a1b457e72 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF660 + void AICState::aiCommandWallAssaultTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + + for (int i = 0; i < 2; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[190 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 190]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp new file mode 100644 index 000000000..403a5138e --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -0,0 +1,234 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2000 + void AICState::aiCreateSiegeUnits(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; + if (locationCount > 3) + locationCount = 3; + + for (int i = 0; i < locationCount; i++) { + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot][i]; + if (location <= 0) + continue; + + int unitID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID; + int uid + = DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid; + int buildingID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID; + + if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + if (buildingID != 0 && DAT_BuildingsState::instance.buildings[buildingID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + + if (unitID == 0 && buildingID == 0) { + if (((short)DAT_TileMapState::instance.BuildingLayer[location]) == 0) { + if (siegeIndex < 2) + continue; + } else { + if (siegeIndex >= 2) + continue; + if (DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType + != OpenSHC::Map::Buildings::BT_TOWER4 + && DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType + != OpenSHC::Map::Buildings::BT_TOWER5) + continue; + } + + int requiredGold + = DAT_BuildingsState::instance + .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType] + .requiredGold; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + + this->aics[aicIndex].defSiegeEngineGoldThreshold + < requiredGold) + continue; + if (DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .delay + < this->aics[aicIndex].defSiegeEngineBuildDelay) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[location]; + int x = location - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (siegeIndex < 2) { + int spawnedUnitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::spawnUnit, + DAT_UnitsState::ptr)(playerID, playerID, x * 8 + 4, y * 8 + 4, + DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .terrainHeightUnk, + ((UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .unitType)); + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = spawnedUnitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[spawnedUnitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::processPlacementResourceLossForBuildingType, + DAT_BuildingsState::ptr)(playerID, + (BuildingType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType, + 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)( + playerID, x, y, + (MappersEnum)(ushort)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .commandBuildingType, + 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail == FALSE) { + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= requiredGold; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .uid + = DAT_BuildingsState::instance.buildings[DAT_TileMapState::instance.placedBuildingID] + .uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = DAT_TileMapState::instance.placedBuildingID; + } + } + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + return; + } + if (unitID != 0 && buildingID == 0) { + int remainingEngineers + = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, + DAT_UnitsState::ptr)(unitID); + if (remainingEngineers <= 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, remainingEngineers); + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + 0) + == 0) + continue; + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + return; + } + + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount == requiredEngineers) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated >= requiredEngineers) { + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp new file mode 100644 index 000000000..218454530 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -0,0 +1,88 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D28B0 + void AICState::aiGiveOuterPatrolCommand(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int aicIndex = aiType - 1; + int i = 0; + int groupsCount = this->aics[aicIndex].OuterPatrolGroupsCount; + if (groupsCount <= 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker + < this->aics[aicIndex].OuterPatrolRallyDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker = 0; + + for (; i < groupsCount; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageShootingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10) + continue; + + int tile; + if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) { + int buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); + if (buildingID != 0) { + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + } else { + int buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) != FALSE) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + else + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp new file mode 100644 index 000000000..113ea823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2A70 + void AICState::aiGiveRaidInstructions(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int i = 0; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter + >= this->aics[aicIndex].RaidRetargetDelay) + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter = 0; + + int totalRaidingTroopsUnk = DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk; + if (totalRaidingTroopsUnk == 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown > 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown--; + + for (; i < 6; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[180 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 180]) + continue; + + int buildingID = 0; + int playerID_askerUnk = DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + playerID_askerUnk) + != 0) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(playerID_askerUnk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown == 0 + && totalRaidingTroopsUnk < MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)(tribeID, i, TRUE) + != FALSE) + continue; + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, this)(playerID, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 182 + || DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 181) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + } else { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + targetBuildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + if (targetBuildingID == 0) { + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + continue; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, + DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + if (buildingID == 0) + continue; + int tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + == FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp new file mode 100644 index 000000000..6165dd546 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D40B0 + void AICState::aiReassignTunnelersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_TUNNELER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 15) + continue; + + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 15); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp new file mode 100644 index 000000000..8c7e889a5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp @@ -0,0 +1,71 @@ +#include "OpenSHC/AI/AICState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2500 + void AICState::aiRecruitEngineerForOilDuty(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 30) + return; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + int smelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + if (smelterID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[smelterID].buildingType != Map::Buildings::BT_OILSMELTER) + return; + int amount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1] + 1; + if (amount <= 1) + return; + + if (DAT_BuildingsState::instance.buildings[smelterID].resources[Game::Resources::RT_PITCH] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_PITCH] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_PITCH] = 4; + return; + } + if (MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, TRUE) + == FALSE) + return; + + int tribeID = MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, FALSE); + int unitID = MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id, playerID, + 0); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + if (unitID == 0) + return; + + DAT_UnitsState::instance.units[unitID].engineerRelatedUnk = 1; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeID].tribeType = Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = Map::Units::STBT_0x41d; + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)(tribeID, + Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, smelterID, + DAT_BuildingsState::instance.buildings[smelterID].uid, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp new file mode 100644 index 000000000..c3ae30af2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp @@ -0,0 +1,69 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD690 + void AICState::aiRecruitSortieMeleeUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitMeleeMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 + >= this->aics[aicIndex].SortieUnitMeleeMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitMelee; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 7; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 167)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp new file mode 100644 index 000000000..951822739 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD560 + void AICState::aiRecruitSortieRangedUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitRangedMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 + >= DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 / 2 + + this->aics[aicIndex].SortieUnitRangedMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitRanged; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 6; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 166)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp new file mode 100644 index 000000000..2f59cee0d --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -0,0 +1,201 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3AE0 + void AICState::aiRecruitUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + bool isExtremeTrail = false; + if (DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME) + isExtremeTrail = true; + + int recruitInterval = (&this->aics[aicIndex] + .RecruitInterval)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int recruitCount = recruitInterval != 0 ? 1 : 2; + if (isExtremeTrail) + recruitCount++; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + recruitCount = 4; + } else { + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker < recruitInterval) + return; + } + + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker = 0; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + int defTotal = this->aics[aicIndex].DefTotal; + if (isExtremeTrail) + defTotal = defTotal * 4 / 3; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) + defTotal = defTotal * 4; + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk >= defTotal) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING + && DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + return; + } + + int recruitDiggingUnit = 0; + if (this->aics[aicIndex].DefDiggingUnitMax != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)(playerID) + != 0) { + int diggerTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (diggerTribeID == 0 + || DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10] + != DAT_TribesState::instance.tribes[diggerTribeID].uid + || DAT_TribesState::instance.tribes[diggerTribeID].size < this->aics[aicIndex].DefDiggingUnitMax) + recruitDiggingUnit = 1; + } + + UnitType unitType = (UnitType)0; + int behaviourType = 0; + for (int i = 0; i < recruitCount; i++) { + if (recruitDiggingUnit && (SEC_RNG::instance.currentNumber2 & 1) != 0) + recruitDiggingUnit = 0; + + if (recruitDiggingUnit) { + if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id == 0) + return; + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + unitType = (UnitType)(&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + unitType = (UnitType)(&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + behaviourType = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::randomlySelectAttackUnitTypeToRecruit, this)(playerID); + unitType = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getUnitTypeForUnitBehaviourType, this)( + playerID, (AIUnitBehaviourType)behaviourType); + } + if (unitType == (UnitType)0) + return; + + int buildingID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_LADDER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].tunnelersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_MONK) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_CATHEDRAL); + else if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER || unitType == OpenSHC::Map::Units::UT_E_MONK + || unitType >= 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment <= 0) + return; + if (DAT_UnitsState::instance.euroUnitAcquisitionFailReason != 2) + return; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + else + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + return; + } + + if (recruitDiggingUnit) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + < this->aics[aicIndex].DefWalls) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToSmallestPatrolTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignUnitToDefensiveTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToItsTribe, this)(unitID, behaviourType); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp new file mode 100644 index 000000000..1d2272d15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1950 + void AICState::aiRegisterTribeAndAssignTarget(int tribeID, int tribeUID) + { + if (DAT_TribesState::instance.tribes[tribeID].size > 0) { + if (DAT_GameState::instance.playerDataArray[DAT_TribesState::instance.tribes[tribeID].owner].aiType + == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 1000; i++) { + int existingTribeID = this->tribeIDArray[i]; + if (existingTribeID != 0 + && DAT_TribesState::instance.tribes[existingTribeID].uid == this->tribeUIDArray[i]) + continue; + + this->tribeIDArray[i] = tribeID; + this->tribeUIDArray[i] = tribeUID; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTargetForTribeBasedOnTargetChoiceLogic, this)( + DAT_TribesState::instance.tribes[tribeID].owner, tribeID); + return; + } + return; + } + DAT_TribesState::instance.tribes[tribeID].tribeState = 3; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp new file mode 100644 index 000000000..7cdacce2c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp @@ -0,0 +1,46 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE4D0 + void AICState::aiRetreatUnits(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeType == 190) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (tribeType == 13) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::selectionContainsHorses, + DAT_TribesState::ptr)(tribeID) + != FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp new file mode 100644 index 000000000..5f0124873 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -0,0 +1,81 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCF0 + BOOLEnum AICState::aiShouldAttackOrWaitForTeamCoordination(int playerID) + { + int otherPlayerID = 1; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + return TRUE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .totalTroopValue + <= 100) + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceBase + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel == 0) + return TRUE; + + int readyTeamMembers = 0; + int unreadyTeamMembers = 0; + for (; otherPlayerID < 9; otherPlayerID++) { + if (otherPlayerID == playerID) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + otherPlayerID) + == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::isAIPlayer, DAT_GameSynchronyState::ptr)( + otherPlayerID) + == FALSE) + continue; + if (DAT_GameState::instance.mapAndTime.playerTeams[otherPlayerID] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) + continue; + + if (DAT_GameState::instance.playerDataArray[otherPlayerID].aiPlayerState != 0) + return TRUE; + if (DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackingEngineerTroops + >= this->aics[DAT_GameState::instance.playerDataArray[otherPlayerID].aiType - 1].AttForceBase + + DAT_GameState::instance.playerDataArray[otherPlayerID].currentWaveRandomAttackingStrength) + readyTeamMembers++; + else + unreadyTeamMembers++; + } + + if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel) + return TRUE; + if (unreadyTeamMembers == 0) + return TRUE; + + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience > 48 + && DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_CAMPAIGN_MISSION) + return TRUE; + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp new file mode 100644 index 000000000..02ab1e06c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB220 + BOOLEnum AICState::aiShouldBuildFarm(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int farmCount + = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countFarms, DAT_BuildingsState::ptr)(playerID, 1); + if (farmCount <= 0) + return TRUE; + if (farmCount >= this->aics[aicIndex].maxFarms) + return FALSE; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / farmCount + >= this->aics[aicIndex].populationPerFarm; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp new file mode 100644 index 000000000..1f0c072e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB4D0 + uint AICState::aiShouldBuildIronMine(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerIronmine <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_IRONMINE, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxIronmines) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerIronmine; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp new file mode 100644 index 000000000..5cf71d7e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB540 + uint AICState::aiShouldBuildPitchrig(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerPitchrig <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_PITCHRIG, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxPitchrigs) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerPitchrig; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp new file mode 100644 index 000000000..2fcefd9ab --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB330 + uint AICState::aiShouldBuildQuarry(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerQuarry <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_QUARRY, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxQuarries) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerQuarry; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp new file mode 100644 index 000000000..7d223c5a9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB290 + BOOLEnum AICState::aiShouldBuildWoodcutter(int playerID) + { + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return FALSE; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerWoodcutter <= 0) + return FALSE; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT, 1); + if (count <= 0) { + count = 1; + } else { + if (count >= this->aics[aicIndex].maxWoodcutters) + return FALSE; + if (count >= 6 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 90) + return FALSE; + if (count >= 3 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 30) + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerWoodcutter; + } +} +} diff --git a/src/OpenSHC/AI/AICState/buyGoods.cpp b/src/OpenSHC/AI/AICState/buyGoods.cpp index 7075afd56..479c46eb4 100644 --- a/src/OpenSHC/AI/AICState/buyGoods.cpp +++ b/src/OpenSHC/AI/AICState/buyGoods.cpp @@ -2,6 +2,7 @@ #include "OpenSHC/Game/GameStateStructures.func.hpp" #include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/Globals/DAT_BuildingsState.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -12,10 +13,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CC000 BOOLEnum AICState::buyGoods(int playerID, ResourceType resourceType, int amount) { - int const cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + int cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( playerID, resourceType, amount); if (MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::processResourceGain, DAT_BuildingsState::ptr)( - playerID, resourceType, amount)) { + playerID, resourceType, amount) + != FALSE) { DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] -= cost; DAT_GameState::instance.playerDataArray[playerID].marketGold -= cost; MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( @@ -24,6 +26,5 @@ namespace AI { } return FALSE; } - } } diff --git a/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp new file mode 100644 index 000000000..6fde84ca6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D39B0 + void AICState::buyRequiredGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + for (int i = 0; i < 25; i++) { + int resourceType = DAT_SkirmishDefinedData::instance.ResourceAcquisitionPreferenceOrder[i]; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && i < 12) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] < 20) + resourceType = Game::Resources::RT_WOOD; + + int amount = DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType]; + if (amount == 0) + continue; + if ((i > 3 + || DAT_GameState::instance.playerDataArray[playerID].popularity + >= this->aics[aicIndex].criticalPopularity) + && DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + && DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + playerID, resourceType, amount)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::requestGoods, this)( + playerID, (ResourceType)resourceType, amount * 2); + continue; + } + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyGoods, this)( + playerID, (ResourceType)resourceType, amount) + != FALSE) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType] = 0; + return; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp new file mode 100644 index 000000000..2b8922be6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD250 + BOOLEnum AICState::canNavigateUnitsFromTileToTargetTile(int tribeID, int tile) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + int fromArea = (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile]; + return MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)( + owner, fromArea, (short)DAT_TileMapState::instance.PathConnectionLayer[tile], 0) + != 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp new file mode 100644 index 000000000..94ed39aaf --- /dev/null +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDB20 + int AICState::chooseRandomBuildingIDFromPlayersArrayOfBuildings(int playerID) + { + int chosenBuildingID = 0; + int chosenPriority = 46; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int rng = SEC_RNG::instance.currentNumber2 & 7; + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) + continue; + + for (int j = 0; j < chosenPriority; j++) { + if (rng <= 1) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]) + continue; + } else if (rng <= 3) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]) + continue; + } else { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + continue; + } + + chosenBuildingID = buildingID; + chosenPriority = j; + } + } + return chosenBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp new file mode 100644 index 000000000..9eb65d0be --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAC70 + void AICState::clearPlayerDataInformationChunk() + { + for (int i = 0; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1 + || DAT_GameSynchronyState::instance.currentAIArray[i] == 0) + MACRO_CALL(OpenSHC::OS_Func::_memset)(&DAT_GameState::instance.playerDataArray[i].aiFarmIndex, 0, 3748); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp new file mode 100644 index 000000000..09533eb02 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE790 + void AICState::clearRallyFlagOnAllAIAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp new file mode 100644 index 000000000..83e39cbc1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp @@ -0,0 +1,40 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF7C0 + void AICState::commandFourTribesToMove(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 3; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[186 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 186]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp new file mode 100644 index 000000000..4a8f8f26d --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2EC0 + void AICState::computeAttackPossibilities(int playerID) + { + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIInfoLayer); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackPrimeInfo, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeTargetedBuildingTilesArray, + DAT_TroopValueState::ptr)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeAttackInfo, DAT_TroopValueState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::computeSiegeSpotScores, DAT_TroopValueState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setScale3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setTown2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setGate2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setWide3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setMoat3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp new file mode 100644 index 000000000..375733aef --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 + void AICState::computeEnemyKeepApproachTile(int playerID) + { + int keepY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + this->aiBorderTilesIndex = 0; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 8000, '\0', this->aiBorderTiles); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + int tile = this->aiBorderTiles[i].tile; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + keepY, + tile + - DAT_ViewportRenderState::instance + .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] + .addXgetTile, + DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]); + this->aiBorderTiles[i].distance = DAT_TileMapState::instance.unitDeathHeatMap[tile] + + DAT_DirectionAlgorithmState::instance.distanceHigh; + } + + int shortestDistanceTile = 0; + int shortestDistance = 100000; + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + if (this->aiBorderTiles[i].distance < shortestDistance) { + shortestDistanceTile = this->aiBorderTiles[i].tile; + shortestDistance = this->aiBorderTiles[i].distance; + } + } + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = 0; + if (shortestDistanceTile <= 0) + return; + + short y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY = y; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = shortestDistanceTile; + int x = shortestDistanceTile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y + = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY; + + if ((short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX] + != 0) + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)( + (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x, + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y, -1, -1, 2000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::storeDestinationOptionsUnk, + DAT_PathFindingState::ptr)(playerID - 1); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp new file mode 100644 index 000000000..4395e208d --- /dev/null +++ b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp @@ -0,0 +1,26 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC910 + int AICState::createTribeForUnitType(int playerID, int unitSelectionTypeIndex) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp new file mode 100644 index 000000000..48f809905 --- /dev/null +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -0,0 +1,154 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1A00 + void AICState::decideOnTribeAttackLocation(uint tribeID) + { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) != FALSE) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + return; + } + + int targetPlayer = DAT_TribesState::instance.tribes[tribeID].targetPlayer_outpost; + if (DAT_TribesState::instance.tribes[tribeID].randomNumber == 0) { + DAT_TribesState::instance.tribes[tribeID].randomNumber = SEC_RNG::instance.currentNumber2; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + } + + DAT_TribesState::instance.tribes[tribeID].counter++; + if (DAT_TribesState::instance.tribes[tribeID].counter + == DAT_TribesState::instance.tribes[tribeID].randomNumber % 6 + 7) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + byte moatWasHit = 0; + int x = 0; + int y = 0; + int unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::getUnitIDForIndexInTribe, DAT_TribesState::ptr)(tribeID, 0); + if (unitID != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::pathFindingToAttackCastleIncludingMoat, + DAT_PathFindingState::ptr)(DAT_TribesState::instance.tribes[tribeID].owner, targetPlayer, + DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y, &moatWasHit, &x, + &y)) { + if (moatWasHit != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, (UnitInstructionType)35, + DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile + x, 0, 0); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_FILL_MOAT, x, y, 1000); + return; + } + } + + if (DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 182 + && DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 185) { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, FALSE) != FALSE) + return; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + return; + } + + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + if (buildingID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = buildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + return; + } + + int destinationX = DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + int destinationY = DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, + DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile + destinationX) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + return; + } + + int buildingID = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + int destinationTile; + if (buildingID == 0) { + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } else if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, + DAT_PathFindingState::ptr)(tribeID, buildingID, 1, + (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile], + targetPlayer); + destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; + if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } + + if (destinationTile == 0) + return; + + int destinationY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[destinationTile]; + int destinationX + = destinationTile - DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)( + tribeID, destinationTile) + == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, targetPlayer); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyHouse.cpp b/src/OpenSHC/AI/AICState/destroyHouse.cpp new file mode 100644 index 000000000..acaa30e30 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyHouse.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB810 + BOOLEnum AICState::destroyHouse(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation + > DAT_GameState::instance.playerDataArray[playerID].populationCap) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation <= 8) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk <= 20) + return FALSE; + + int hovelID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_HOVEL); + if (hovelID == 0) + return FALSE; + + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(hovelID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(hovelID); + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/digNearestMoat.cpp b/src/OpenSHC/AI/AICState/digNearestMoat.cpp new file mode 100644 index 000000000..a8f101b79 --- /dev/null +++ b/src/OpenSHC/AI/AICState/digNearestMoat.cpp @@ -0,0 +1,54 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD160 + void AICState::digNearestMoat(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10]) + return; + if (DAT_TribesState::instance.tribes[tribeID].size <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].percentageSomething > 80) + return; + + if (MACRO_CALL_MEMBER(Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)( + playerID) + <= 0) { + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + return; + } + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + int tile = MACRO_CALL_MEMBER(Map::TileMapState_Func::findNearestFriendlyMoatTileForDigging, + DAT_TileMapState::ptr)(playerID, targetUnitID, 1); + if (tile <= -1) + return; + + MACRO_CALL_MEMBER(Map::TileMapState_Func::setXYBasedOnMoatID, DAT_TileMapState::ptr)( + tile, 1, DAT_UnitsState::instance.units[targetUnitID].x, DAT_UnitsState::instance.units[targetUnitID].y); + if (MACRO_CALL_MEMBER(Map::Units::TribesState_Func::updateTribeRallyFlags, DAT_TribesState::ptr)(tribeID) != 0) + return; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + Map::Units::UIT_DIG_MOAT, DAT_TileMapState::instance.ALG_MoatXResult, + DAT_TileMapState::instance.ALG_MoatYResult, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + } +} +} diff --git a/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp new file mode 100644 index 000000000..19151eaa5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFB20 + void AICState::disbandAIPlayerSiegeUnits(int playerID) + { + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TOWER + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_BATTERINGRAM + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_SHIELD + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + DAT_UnitsState::instance.units[unitID].tribeID, OpenSHC::Map::Units::UIT_EXIT_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + DAT_UnitsState::instance.units[unitID].state.generic = OpenSHC::Map::Units::States::US_DISAPPEAR; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp new file mode 100644 index 000000000..fd1ac266c --- /dev/null +++ b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp @@ -0,0 +1,53 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCBE0 + int AICState::getDefensiveTribeForUnit(int playerID, int unitID) + { + int index; + for (index = 0; index < 20; index++) { + if (DAT_UnitsState::instance.units[unitID].unitType + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index] + || DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index]) + break; + } + + int tribeIndex; + if (index < 1) + tribeIndex = 180; + else if (index < 2) + tribeIndex = 181; + else if (index < 7) + tribeIndex = 182; + else if (index < 12) + tribeIndex = 183; + else if (index < 15) + tribeIndex = 184; + else if (index < 20) + tribeIndex = 185; + else + return 0; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp new file mode 100644 index 000000000..00d41635a --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp @@ -0,0 +1,50 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEB50 + void AICState::giveMoveCommandToAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeType == 18 || tribeType == 190 || tribeType == 15) + continue; + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = (char)(SEC_RNG::instance.currentNumber2 & 1); + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp new file mode 100644 index 000000000..c094f57f1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF920 + undefined4 AICState::giveSomeRaidCommand(int tribeIndex, int tribeType) + { + int owner = DAT_TribesState::instance.tribes[tribeIndex].owner; + int targetBuildingID = DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID; + if (tribeType == 186 || tribeType == 18 || tribeType == 14 || tribeType == 13) + return 0; + + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0) + return 1; + + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = 0; + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[owner].attackedPlayerID); + if (buildingID == 0) + return 0; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::queueDisbandAndAttackCommand4Params, + DAT_UnitsState::ptr)(tribeIndex, 9, buildingID, DAT_BuildingsState::instance.buildings[buildingID].uid); + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + DAT_TribesState::instance.tribes[tribeIndex].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = buildingID; + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp new file mode 100644 index 000000000..ccf876a64 --- /dev/null +++ b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp @@ -0,0 +1,226 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using namespace OpenSHC::Game::Resources; + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBA50 + void AICState::planToBuyWhenLowOnResourceAndSnoozeBuildings(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int allowIronMines = 1; + int allowArmourers = 1; + int allowFletchers = 1; + int allowPitchRigs = 1; + int allowWoodcutters = 1; + int allowPoleturners = 1; + int allowBlacksmiths = 1; + int allowTanners = 1; + int allowQuarries = 1; + int allowBakeries = 1; + int allowBreweries = 1; + int allowInns = 1; + int allowFarms = 1; + int allowWells = 1; + + int buildingBurning = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::playerHasBurningBuilding, DAT_BuildingsState::ptr)(playerID); + int wood = DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_WOOD]; + + if (wood <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_IRON] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_FLOUR] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_HOPS] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_ALE] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk = 0; + } + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter++; + + if (DAT_GameState::instance.playerDataArray[playerID].countFletchersPoleturners <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countArmorersAndBlacksmiths <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBakers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBrewers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + + int population = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (population < 10 + && (DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + || DAT_GameState::instance.playerDataArray[playerID].countWoodcutters > 2)) { + allowQuarries = 0; + } + + if (wood >= 20 && population < 10 && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood < 8) { + allowWoodcutters = 0; + } + + if (buildingBurning == 0 + && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers + >= DAT_GameState::instance.playerDataArray[playerID].countFarms + && DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_GOLD] < 500) { + allowWells = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].someResourceCounter > 36) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36) { + allowBakeries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72) { + allowBreweries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk > 72) { + allowInns = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + allowFletchers = 1; + allowIronMines = 0; + allowQuarries = 0; + allowPitchRigs = 0; + allowFarms = 0; + allowWoodcutters = 0; + allowPoleturners = 1; + allowBlacksmiths = 1; + allowArmourers = 1; + allowTanners = 1; + allowBakeries = 0; + allowBreweries = 0; + allowInns = 0; + } + + if (allowWells) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = true; + } + + if (allowQuarries) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = true; + } + + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_PITCHRIG] = !allowPitchRigs; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_POLETURNER] = !allowPoleturners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_ARMOURER] = !allowArmourers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_TANNER] = !allowTanners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WOODCUTTERSHUT] = !allowWoodcutters; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BAKERY] = !allowBakeries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BREWERY] = !allowBreweries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_INN] = !allowInns; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; + + if (allowFarms) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = true; + } + + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] = 5; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] = 2; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp new file mode 100644 index 000000000..403a03302 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B10 + void AICState::playNervous2BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown != 0) { + return; + } + + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int currentPlayerID = DAT_GameSynchronyState::instance.currentPlayerSlotID; + DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown = 48; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[currentPlayerID]) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType - 1, 9); + } +} +} diff --git a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp new file mode 100644 index 000000000..d72a3a55f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -0,0 +1,162 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/Unit.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Unit; + using OpenSHC::Map::Units::UnitInstructionType; + using OpenSHC::Map::Units::UnitLogicState; + using OpenSHC::Map::Units::UnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD2A0 + void AICState::recruitHarrassingSiegeEngines(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int engineersGuild = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + + if (aiType == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 500) { + return; + } + + int aicIndex = aiType - 1; + int harrassingSiegeEnginesMax = this->aics[aicIndex].HarassingSiegeEnginesMax; + if (harrassingSiegeEnginesMax <= 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout != 0) { + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout -= 1; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harassingSiegeEnginesCountUnk + >= harrassingSiegeEnginesMax) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].unknownHarrassingSiegeRelated >= 20) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + || DAT_TribesState::instance.tribes[tribeID].size < 2) { + // Not enough engineers yet: recruit one at the engineers guild + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0 + || DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 30 + || engineersGuild <= 0 + || DAT_BuildingsState::instance.buildings[engineersGuild].buildingType + != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) { + return; + } + + uint unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + OpenSHC::Map::Units::UT_E_ENGINEER, engineersGuild, playerID, 0); + if (unitID == 0) { + return; + } + + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165]) { + tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + = DAT_TribesState::instance.tribes[tribeID].uid; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, tribeID); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = OpenSHC::AI::AIUBT_HARASS_SIEGE; + } else { + // Enough engineers: pick the next siege engine from the AIC list and place its tent + if (DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex >= 8) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + if ((&this->aics[aicIndex].HarassingSiegeEngine1)[DAT_GameState::instance.playerDataArray[playerID] + .harrassingSiegeEnginesIndex] + == 0) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + + int engineIndex = DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex; + MappersEnum siegeEngineToBuild = (MappersEnum)(&this->aics[aicIndex].HarassingSiegeEngine1)[engineIndex]; + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = engineIndex + 1; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AIVState_Func::findSpotNearEngineersGuild, DAT_AIVState::ptr)(playerID) + == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + + // Place the siege tent + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + DAT_AIVState::instance.buildingApproriateGridXPosition * 5, + DAT_AIVState::instance.buildingAppropriateGridYPosition * 5, siegeEngineToBuild, 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail != FALSE) { + return; + } + + int buildingID = DAT_TileMapState::instance.placedBuildingID; + DAT_BuildingsState::instance.buildings[buildingID].attackWave = 0; + DAT_BuildingsState::instance.buildings[buildingID].unknownSiegeTentRelated01 = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + } + + // Point all harassing siege engines at the currently attacked player + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != OpenSHC::Map::Units::ULS_INVISIBLE + && DAT_UnitsState::instance.units[unitID].dying == 0 + && (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + && DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == OpenSHC::AI::AIUBT_SIEGE_01) { + DAT_UnitsState::instance.units[unitID].siegeTargetPlayerID + = (short)DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + } + } + + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout = 8; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp new file mode 100644 index 000000000..7e3fe3be9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/AI/AIC/AICSpecification.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Text/TextAlignment.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BlendingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_RenderingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AICState; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::AI::AIType; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Text::TextAlignment; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1320 + void AICState::renderDebugDataAiInfo(int x, int y, int width, int height) + { + int playerID = 1; + char text[1000]; + + // One row of 24 pixels per player, starting one row below y + y += 24; + for (; playerID <= 8; playerID++, y += 24) { + if (DAT_GameState::instance.playerDataArray[playerID].playerDeathRelated != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL + && DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + continue; + } + + uint color = DAT_RenderingDefinedData::instance + .ColorArray[DAT_BlendingDefinedData::instance.PlayerSlotUnitColor[playerID]]; + + // Player number and name + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumberToScreen2, DAT_TextManagerObject::ptr)( + playerID, x + 6, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderTextToScreen, DAT_TextManagerObject::ptr)( + "-", x + 16, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + DAT_GameSynchronyState::instance.DAT_PlayerNames[playerID], x + 26, y + 1, OpenSHC::Text::TTA_LEFT, + color, 0, 18, FALSE, 0); + + // Strength feeling + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling == OpenSHC::AI::AIST_WEAK) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Weak", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling + == OpenSHC::AI::AIST_DEFAULT) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Normal", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Strong", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Current AIV pause + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + "P:", x + 262, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumber2, DAT_TextManagerObject::ptr)( + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause, x + 264, y, OpenSHC::Text::TTA_LEFT, + color, 0, 18, TRUE, 0); + + // Attack target and coordination level + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Atk:%d W:%d", + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 302, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // fixme: Castle (AIV); the castle ID is passed even for human players, whose format ignores it + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV:n/a", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV: %d", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 382, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Engineers + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "E:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6, + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].SortieUnitRangedMin); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 452, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Siege troops (attack troops without engineers): attack ticker in white when not attacking, + // otherwise the attack force target + int siegeTroops = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops; + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)( + text, "%d/%d", siegeTroops, DAT_GameState::instance.playerDataArray[playerID].attackTicker); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, 0xffffff, 0, 18, FALSE, 0); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Sg:%d/%d", siegeTroops, + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].AttForceBase); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Raiding/harassing troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Hr:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk, + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)( + (DAT_GameState::instance.playerDataArray[playerID].aiType - 1), playerID)); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 592, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Defensive troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Df:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk, + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].DefWalls); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 657, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp new file mode 100644 index 000000000..cacb4823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -0,0 +1,143 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AIC/AITargetChoiceInt.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AITargetChoice; + using OpenSHC::AI::AIC::AITargetChoiceInt; + using OpenSHC::Game::Player::PlayerData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4680 + void AICState::selectAttackTarget(int playerID) + { + int selectedPlayerID = 0; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int fromX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; + int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + int lowestDistance = 10000; + int highestGold = 0; + int lowestPoints = 1000000; + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeNervousness, this)(playerID); + + // Drop a pending request if the asking player's lord is dead + if ((DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + || DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + == 0) { + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = 0; + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) { + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk) + != 0) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; + return; + } + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + + int targetChoice = this->aics[aicIndex].TargetChoice; + if (targetChoice == OpenSHC::AI::AIC::AITC_PLAYER) { + // Closest human player + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[i].currentResources[0xf] >= highestGold) { + highestGold = DAT_GameState::instance.playerDataArray[i].currentResources[0xf]; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[i].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[i].totalTroopValue + + DAT_GameState::instance.playerDataArray[i].currentResources[0xf] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= lowestPoints) { + lowestPoints = points; + selectedPlayerID = i; + } + } + } + + // Fall back to the closest enemy + if (selectedPlayerID == 0) { + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + + if (selectedPlayerID == 0) { + return; + } + } + + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk = selectedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = selectedPlayerID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp new file mode 100644 index 000000000..038b44af9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp @@ -0,0 +1,113 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategory.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategoryInt.hpp" +#include "OpenSHC/AI/Trading/ResourceAICResourceMappingEntry.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::AI::Trading::AIResourceTradeCategory; + using OpenSHC::AI::Trading::AIResourceTradeCategoryInt; + using OpenSHC::AI::Trading::ResourceAICResourceMappingEntry; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1D60 + void AICState::sellExcessGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int playerResourceBase = playerID * (sizeof(PlayerData) / sizeof(int)); + for (int i = 0; i < 20; i++) { + ResourceType resourceType + = (ResourceType)DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].game; + int variance = this->aics[aicIndex].maxResourceVariance; + int resourceOffset = (playerResourceBase + resourceType) * sizeof(int); + + int amount; + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WOOD) { + amount = this->aics[aicIndex].maxWood; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = this->aics[aicIndex].maxFood; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_STONE) { + amount = this->aics[aicIndex].maxStone; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = this->aics[aicIndex].maxEquipment; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_ALE) { + amount = this->aics[aicIndex].maxBeer; + } else { + amount = this->aics[aicIndex].maxResourceOther; + } + amount += variance; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf] < 500) { + // Nervous and low on gold: keep a quarter of the food and up to 10000 weapons, sell everything else + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = amount / 4; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 10000; + } else { + amount = 0; + } + variance = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].popularity + < this->aics[aicIndex].criticalPopularity) { + // Critical popularity: keep all food, sell all weapons, keep a quarter of everything else + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + != OpenSHC::AI::Trading::AIRTC_FOOD) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 0; + } else { + amount = amount / 4; + } + } + variance = 0; + } else { + amount += *( + int*)((char*)DAT_GameState::instance.playerDataArray[0].resourcesToAcquireArray + resourceOffset); + } + + // Resources the AIC always sells are kept at zero + for (int j = 0; j < 15; j++) { + if (resourceType == (&this->aics[aicIndex].sellResource01)[j]) { + amount = 0; + variance = 0; + break; + } + } + + if (*(int*)((char*)DAT_GameState::instance.playerDataArray[0].currentResources + resourceOffset) > amount) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellGoods, this)(playerID, resourceType, + ((int*)DAT_GameState::instance.playerDataArray[0] + .currentResources)[playerResourceBase + resourceType] + - amount + variance); + return; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp new file mode 100644 index 000000000..0296967f4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4340 + void AICState::sendWallPatrolUnitTribesToAIVLocations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 3; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType]; + int groups = count; + if (count > this->aics[aicIndex].defWallPatrolGroups) { + groups = this->aics[aicIndex].defWallPatrolGroups; + } + if (groups <= 0) { + continue; + } + + int stepSize = count / groups; + if (stepSize <= 0) { + stepSize = 1; + } + + for (int j = 0, patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]) { + continue; + } + + int slot + = (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + patrolGroupIndex) + % count; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType, + slot); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp new file mode 100644 index 000000000..ef07f7cf0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp @@ -0,0 +1,40 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDF10 + void AICState::setCurrentAttackRaidParameter(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + + int randomPercent = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk; + int raidUnits = this->aics[aicIndex].RaidUnitsRandom; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 1000) { + raidUnits = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + >= 5000) { + raidUnits *= 2; + } + + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].currentResources[Game::Resources::RT_GOLD] + < 500) { + raidUnits = -(this->aics[aicIndex].RaidUnitsBase / 2); + } + + DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + = (raidUnits * randomPercent) / 100; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp new file mode 100644 index 000000000..45c33ff67 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp @@ -0,0 +1,17 @@ +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1090 + void AICState::setTriggerForAITauntResponse() + { + if (MACRO_CALL_MEMBER(Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) { + this->aiTauntResponseTimeWindow = timeGetTime(); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp new file mode 100644 index 000000000..ce194bb2d --- /dev/null +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -0,0 +1,59 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC990 + int AICState::smallestTribeOfUnitType(int playerID, AIVUnitType unitType) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return 0; + } + + int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + int smallestTribeID = 0; + int smallestIndex = 0; + int smallestSize = 1000; + if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = maxGroups; + } + + int i = 0; + int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; + for (;; i++) { + if (i >= count) { + if (smallestTribeID == 0) + return 0; + break; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i]) { + smallestTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + smallestIndex = i; + break; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + smallestTribeID = tribeID; + smallestIndex = i; + } + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + smallestIndex] = (short)smallestTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + smallestIndex] + = DAT_TribesState::instance.tribes[smallestTribeID].uid; + return smallestTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp new file mode 100644 index 000000000..0a37073ba --- /dev/null +++ b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC7B0 + void AICState::syncBehaviourType7UnitTribes(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 7) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp new file mode 100644 index 000000000..5050ed89e --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp @@ -0,0 +1,283 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Audio/SFX/SFXState.func.hpp" +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SFXState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D49E0 + void AICState::updateAIPlayerState(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + // an ally asked for help and is still alive: drop everything and go to their keep + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk != 1 + && DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToPlayerIDsKeepIfPossible, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk); + return; + } + + int _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + if (_aiPlayerState == 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( + playerID)) { + // send own units back to keep or campfire + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRetreatUnits, this)(playerID); + return; + } + + // proceed with attack + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); + // fixme: struct layout. This looks like an int[10][8] attack tracker starting at + // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). + (&DAT_GameState::instance + .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .attackedByPlayerArrayUnk)[playerID * 8]++; + } else if ((MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID) + && _aiPlayerState != 9) + || (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && _aiPlayerState <= 5) + || DAT_GameState::instance.playerDataArray[playerID].attackTicker >= 200) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 8; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + + if (!MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // attacked player is dead + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState <= 3) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)( + playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState < 7) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 7; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearOutdatedAITribes, this)(playerID); + + _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + + if (_aiPlayerState == 1) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recreateEngineersTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::initializeGatePathfindingForOwner, + DAT_BuildingsState::ptr)(playerID); + // computes the first siege location for units + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeEnemyKeepApproachTile, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile > 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackTribesToLocations, this)(playerID); + return; + } + // no approach tile found: back to idle + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + return; + } + + if (_aiPlayerState == 2) { + // gathering: wait until enough of the attack force has rallied + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiChoiceAttForceRallyPercentage, this)(playerID)) + return; + // send the attacking units to move closer + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 3; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setNextMoveLocationForUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0xe_toPlaceTunnels, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playTauntToAttackedPlayerBikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 3) { + // send the attacking units closer to the castle + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::processAttForceRallyPercentage, this)(playerID)) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 4; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + return; + } + + if (_aiPlayerState == 4) { + // siege: wait AttAssaultDelay (skipped without siege engines) before the assault + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::countSiegeEngineUnits, this)(playerID) <= 0) + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 100; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue++; + if (DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue + <= this->aics[_aicIndex].AttAssaultDelay) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Audio::SFX::SFXState_Func::playSFXAtLocationFullVolume, DAT_SFXState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, 0x14); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // castle is closed: attack the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe11StandGroundToGate, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe13DefensiveWave, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 5) { + // attack walls + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01++; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 >= 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandWallAssaultTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0x11ToAggressiveAndAttackIfConditionMet, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setEightTribe0xc0TribesToAggressiveAndAttack, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xCtoAggressiveAndAttack, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xbToAggressiveAndAttack, this)(playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + && !MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 6) { + // enemy castle breached + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + int _attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(_attackedPlayerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToAttackBreachedCastle, this)(playerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + // the breach got closed again: back to attacking the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + + if (_aiPlayerState == 7) { + // target died mid-attack: linger for AttUnitPatrolRecommandDelay, then retreat + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue++; + if (DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue + <= this->aics[_aicIndex].AttUnitPatrolRecommandDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + return; + } + + if (_aiPlayerState == 8) { + // retreat from attack + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playAnger1BikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 9) { + // retreating: back to idle once the units are home (or after 20 ticks) + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker < 20 + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::percentageNonMovingTribesGTEAICSpecified, this)( + playerID) + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp new file mode 100644 index 000000000..f302283a6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp @@ -0,0 +1,214 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategory.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategoryInt.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AINervousDestroyBuildingCategory; + using OpenSHC::AI::AINervousDestroyBuildingCategoryInt; + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::Game::GameMode2; + using OpenSHC::Game::TrailType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3320 + void AICState::updateAIStrengthState(int playerID, int aiRecruitUnitChoicePreference) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == 0) + return; + + if ((char)aiRecruitUnitChoicePreference == 0) { + if (DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER) + return; + if (DAT_GameCore::instance.isSkirmishTrail != TRUE) + return; + if (DAT_GameCore::instance.currentTrailType != OpenSHC::Game::TT_EXTREME) + return; + } + + int _aicIndex = _aiType - 1; + + int _currentGold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + if (_currentGold < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + else if (_currentGold < 2000) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 2; + + int _currentPopulation = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (_currentPopulation >= 15) { + if (_currentPopulation < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalFood = DAT_GameState::instance.playerDataArray[playerID].totalFood; + if (_totalFood >= 20) { + if (_totalFood < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalTroopCount = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk + + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk; + if (_totalTroopCount >= 8) { + if (_totalTroopCount < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 4; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 2) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 7) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_DEFAULT; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_STRONG; + + // while the first AIV pause is still running, the AI always feels weak + if (DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[playerID] + .aivPauses[DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex] + > 0) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + + // the three recruitProb* triples are laid out as Default, Weak, Strong, matching AIStrengthType + int _recruitProbDefensive = (&this->aics[_aicIndex] + .recruitProbDefDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbAttack = (&this->aics[_aicIndex] + .recruitProbAttackDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbRaid = (&this->aics[_aicIndex] + .recruitProbRaidDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _totalRecruitProb = _recruitProbAttack + _recruitProbRaid + _recruitProbDefensive; + + int _aiRecruitUnitChoice; + if (_totalRecruitProb != 0) { + int _random = SEC_RNG::instance.currentNumber2 % _totalRecruitProb; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + if ((int)DAT_GameCore::instance.mapTimeInTicks < 4800) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbRaid + _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_RAIDING; + else + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else { + _aiRecruitUnitChoice = aiRecruitUnitChoicePreference; + } + + if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(_aicIndex, playerID)) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + >= this->aics[_aicIndex].DefTotal) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState != _aiRecruitUnitChoice) + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = _aiRecruitUnitChoice; + + if ((char)aiRecruitUnitChoicePreference == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, TRUE)) { + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 0; + return; + } + + int _gold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_gold > 500) { + if (_gold > 2500) { + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter++; + if ((DAT_GameState::instance.playerDataArray[playerID].aiStateCounter & 1) + && DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy + <= 20) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::shareGoldAmongTeamMembers, this)( + playerID, _gold / 10); + } + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 5) { + // unreachable in the original as well: anything <= 5 is already handled by the <= 7 branch + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 1) + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker--; + return; + } + + int _destroyChoice = DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker; + if (_destroyChoice > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 0; + return; + } + + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[2] = 20; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_destroyChoice == 8) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + } else if (_destroyChoice == 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (_destroyChoice == 6) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_GATEHOUSES_TOWERS_GUILDS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_BARRACKS_MERCPOST_STABLES); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WATER_BUILDINGS); + } else if (_destroyChoice == 5) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } else if (_destroyChoice == 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FARMS); + } + + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker--; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, FALSE)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous2BikFromPlayer, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 8; + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter = 0; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 8; + } + } +} +} From 8251a29ea7cb4f06e7d8cde2bcc024e6cf6f7a0f Mon Sep 17 00:00:00 2001 From: Gynt Date: Wed, 14 Oct 2026 09:42:47 +0200 Subject: [PATCH 02/23] add status file --- status/addresses-SHC-3BB0A8C1.txt | 354 +++++++++++++++--------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f6e47278b..19c7e8b9e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34157,73 +34157,73 @@ SHC_3BB0A8C1_0x004C6CD0 | 0.0% | Pending SHC_3BB0A8C1_0x004C6D30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C6D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004C6D60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7520 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7910 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7910 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8110 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8530 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8530 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8920 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8920 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C94F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C94F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9900 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9900 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA4D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA8A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA8A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CAC70 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAC70 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CACB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CACB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CAD10 | 0.0% | Pending SHC_3BB0A8C1_0x004CAE20 | 0.0% | Pending -SHC_3BB0A8C1_0x004CAEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAEA0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB060 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB150 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB150 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB1B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB1B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB220 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB220 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CB290 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB290 | 98.1% | Reimplemented -SHC_3BB0A8C1_0x004CB330 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB330 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB3A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB3A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB4D0 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB540 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB540 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB5B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB5B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB610 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB610 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB66C | 0.0% | Pending @@ -34501,7 +34501,7 @@ SHC_3BB0A8C1_0x004CB6FA | 0.0% | Pending SHC_3BB0A8C1_0x004CB6FB | 0.0% | Pending -SHC_3BB0A8C1_0x004CB700 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB700 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB754 | 0.0% | Pending @@ -34845,55 +34845,55 @@ SHC_3BB0A8C1_0x004CB803 | 0.0% | Pending SHC_3BB0A8C1_0x004CB804 | 0.0% | Pending -SHC_3BB0A8C1_0x004CB810 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB810 | 92.3% | Reimplemented -SHC_3BB0A8C1_0x004CB890 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB890 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB940 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB9F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBA50 | 90.7% | flag slot order differs -SHC_3BB0A8C1_0x004CBE30 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBE30 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CBFA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC000 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004CC000 | 92.9% | Reimplemented -SHC_3BB0A8C1_0x004CC070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC390 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC420 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC420 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC520 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC5A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC5A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC680 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC680 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC720 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC720 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC7B0 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC840 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC840 | 94.3% | Reimplemented -SHC_3BB0A8C1_0x004CC8D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC8D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC910 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented -SHC_3BB0A8C1_0x004CC990 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC990 | 56.6% | regalloc/locals differ -SHC_3BB0A8C1_0x004CCAF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CCBE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCD20 | 66.0% | frame and regalloc differ SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending @@ -35261,289 +35261,289 @@ SHC_3BB0A8C1_0x004CCFA0 | 0.0% | Pending SHC_3BB0A8C1_0x004CCFA1 | 0.0% | Pending -SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCFB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD160 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD160 | 91.7% | Register allocation differs -SHC_3BB0A8C1_0x004CD250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD250 | 94.7% | Reimplemented -SHC_3BB0A8C1_0x004CD2A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD2A0 | 59.60% | Reimplemented functionally, code block ordering problem -SHC_3BB0A8C1_0x004CD560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CD690 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented -SHC_3BB0A8C1_0x004CD7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD7B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD8F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD8F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDAB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDB20 | 44.4% | regalloc/spill differs -SHC_3BB0A8C1_0x004CDBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCF0 | 77.2% | regalloc/counters differ -SHC_3BB0A8C1_0x004CDE60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDF10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDF10 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004CDFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE040 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE040 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE1A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE1A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE200 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE200 | 64.5% | spilled local differs SHC_3BB0A8C1_0x004CE270 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE3B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE3B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE430 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE430 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE4D0 | 93.8% | Reimplemented SHC_3BB0A8C1_0x004CE530 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE5F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE5F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE660 | 0.0% | Pending SHC_3BB0A8C1_0x004CE6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE790 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE790 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004CE830 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE860 | 0.0% | Pending SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE930 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE930 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEA50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEB50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEB50 | 98.8% | Reimplemented SHC_3BB0A8C1_0x004CEB80 | 0.0% | Pending -SHC_3BB0A8C1_0x004CEC80 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEC80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CED90 | 0.0% | Pending +SHC_3BB0A8C1_0x004CED90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEEA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEFD0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF060 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF270 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF2E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF2E0 | 94.7% | esi/edi regalloc differs -SHC_3BB0A8C1_0x004CF400 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF400 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF470 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF4F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF4F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF560 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF660 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF660 | 97.2% | Reimplemented -SHC_3BB0A8C1_0x004CF6E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF6E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF750 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF750 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF7C0 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF7C0 | 98.2% | Reimplemented SHC_3BB0A8C1_0x004CF7F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF920 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF920 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFA10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFA10 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CFBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFCD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFF60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D00A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D00A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0190 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0190 | 100.0% | Reimplemented, compiler didn't optimize one tail -SHC_3BB0A8C1_0x004D0280 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0280 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0520 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0580 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0580 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D05D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D05D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0720 | 0.0% | Pending -SHC_3BB0A8C1_0x004D07C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D07C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0830 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D08B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D08B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0910 | 0.0% | Pending -SHC_3BB0A8C1_0x004D0A20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0A80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0B10 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B10 | 68.0% | Load scheduling differs -SHC_3BB0A8C1_0x004D0B70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0BB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0BB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0D40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0D40 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0DB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0DB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E50 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0F40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F40 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0F90 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0FC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0FC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1000 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1050 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1050 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1090 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1090 | 90.0% | Reimplemented -SHC_3BB0A8C1_0x004D10B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D10B0 | 100.0% | Reimplemented, one reordered push edi SHC_3BB0A8C1_0x004D11C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D12A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1320 | 72.3% | Style pass, asm differs SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending SHC_3BB0A8C1_0x004D1710 | 0.0% | Pending -SHC_3BB0A8C1_0x004D18B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D18B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1950 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004D1A00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation -SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2000 | 41.9% | regalloc/frame differ SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending SHC_3BB0A8C1_0x004D20C0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D2500 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented -SHC_3BB0A8C1_0x004D2660 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2660 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2730 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2730 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2790 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D2790 | 91.3% | Reimplemented -SHC_3BB0A8C1_0x004D27E0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D27E0 | 92.0% | Reimplemented SHC_3BB0A8C1_0x004D2830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2870 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D28B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D28B0 | 86.3% | select call block layout diffe -SHC_3BB0A8C1_0x004D2A70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2A70 | 54.3% | regalloc/spills differ -SHC_3BB0A8C1_0x004D2E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs -SHC_3BB0A8C1_0x004D2F80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D30E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3320 | 97.2% | ebp/ebx regalloc differs -SHC_3BB0A8C1_0x004D3780 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3850 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3850 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D38C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D38C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3960 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D39B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3AE0 | 43.4% | regalloc/spills differ -SHC_3BB0A8C1_0x004D3F20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented -SHC_3BB0A8C1_0x004D4040 | 100.0% | Reimplemented, for loop didn't work +SHC_3BB0A8C1_0x004D4040 | 97.0% | Reimplemented -SHC_3BB0A8C1_0x004D40B0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D40B0 | 94.9% | Reimplemented SHC_3BB0A8C1_0x004D4130 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4220 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4220 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4340 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4340 | 54.63% | Reimplemented, different register usage SHC_3BB0A8C1_0x004D4400 | 0.0% | Pending -SHC_3BB0A8C1_0x004D44E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D44E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4530 | 0.0% | Pending @@ -35551,15 +35551,15 @@ SHC_3BB0A8C1_0x004D45E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4630 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4680 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4680 | 64.80% | Stuck, register usage SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending -SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs -SHC_3BB0A8C1_0x004D4940 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D49E0 | 52.84% | Register allocation differences, logic checks out SHC_3BB0A8C1_0x004D5010 | 100.0% | Reimplemented @@ -35571,7 +35571,7 @@ SHC_3BB0A8C1_0x004D5370 | 0.0% | Pending SHC_3BB0A8C1_0x004D53A0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D53E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D53E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D55D0 | 0.0% | Pending From fa386a11ded49f5d120e3051fdb753a74438468a Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:42 +0200 Subject: [PATCH 03/23] AICState: match aiShouldBuild* parameter type to the generated header The header declares the parameter as int; the PlayerID typedef the definitions used is not visible there and broke the build. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JP48NBCkB9vy6M7zLArtgo --- src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp | 2 +- src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp | 2 +- src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp index 1f0c072e0..7f4810a3e 100644 --- a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp @@ -10,7 +10,7 @@ namespace OpenSHC { namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB4D0 - uint AICState::aiShouldBuildIronMine(PlayerID playerID) + uint AICState::aiShouldBuildIronMine(int playerID) { int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == AITA_NULL) diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp index 5cf71d7e0..2356560a1 100644 --- a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp @@ -10,7 +10,7 @@ namespace OpenSHC { namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB540 - uint AICState::aiShouldBuildPitchrig(PlayerID playerID) + uint AICState::aiShouldBuildPitchrig(int playerID) { int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == AITA_NULL) diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp index 2fcefd9ab..468d283d2 100644 --- a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp @@ -10,7 +10,7 @@ namespace OpenSHC { namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB330 - uint AICState::aiShouldBuildQuarry(PlayerID playerID) + uint AICState::aiShouldBuildQuarry(int playerID) { int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == AITA_NULL) From a013994e97db601dab7a79ca3daf7302deeec625 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:43 +0200 Subject: [PATCH 04/23] reimplement: SHC_3BB0A8C1_0x004CCD20 80.13% Register allocation differs --- .../addUnitToSmallestBehaviourTypeTribe.cpp | 25 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp index 5d3e5ddf3..fc7589ea6 100644 --- a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -18,8 +18,12 @@ namespace AI { return 0; int aicIndex = aiType - 1; + int firstSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + int selectedTribeID = 0; + int selectedSlot = 0; + int smallestSize = 1000; int groupsCount; - switch (DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]) { + switch (firstSlot) { case 15: groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; break; @@ -36,25 +40,20 @@ namespace AI { groupsCount = 1; break; } + if (groupsCount < 1) + groupsCount = 1; - int selectedTribeID = 0; - int selectedSlot = 0; - if (groupsCount <= 1) { - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[DAT_SkirmishDefinedData::instance - .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]; + if (groupsCount == 1) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[firstSlot]; if (tribeID != 0 && DAT_TribesState::instance.tribes[tribeID].uid - == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[DAT_SkirmishDefinedData::instance - .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]) + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[firstSlot]) return tribeID; selectedTribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); - selectedSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + selectedSlot = firstSlot; } else { - int smallestSize = 1000; - for (int slot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; slot - < DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10] + groupsCount; - slot++) { + for (int slot = firstSlot; slot < firstSlot + groupsCount; slot++) { int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[slot]; if (tribeID == 0 || DAT_TribesState::instance.tribes[tribeID].uid diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 19c7e8b9e..7b45e579b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34893,7 +34893,7 @@ SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 66.0% | frame and regalloc differ +SHC_3BB0A8C1_0x004CCD20 | 80.1% | Register allocation differs SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending From 25fd3eb0bd4d152d2632bf2199d8d927e6ce015d Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:43 +0200 Subject: [PATCH 05/23] reimplement: SHC_3BB0A8C1_0x004CF2E0 100.00% Reimplemented --- .../aiCommandTribe12AggressiveApproach.cpp | 39 +++++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp index 818b68d3c..1f7f758d7 100644 --- a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -36,22 +36,29 @@ namespace AI { DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, - DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 200) - == FALSE - && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, - DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) - == FALSE - && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, - DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100) - == FALSE - && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, - DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50) - == FALSE) - return; - - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( - tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, - OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 200)) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } else if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150)) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } else if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100)) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } else if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50)) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7b45e579b..186d08c56 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35355,7 +35355,7 @@ SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF2E0 | 94.7% | esi/edi regalloc differs +SHC_3BB0A8C1_0x004CF2E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF400 | 100.0% | Reimplemented From be606de94a43a2168889b2e22ad17adc4978e00b Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:44 +0200 Subject: [PATCH 06/23] reimplement: SHC_3BB0A8C1_0x004D28B0 100.00% Reimplemented --- .../AI/AICState/aiGiveOuterPatrolCommand.cpp | 20 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp index 218454530..aab39a342 100644 --- a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -45,7 +45,16 @@ namespace AI { continue; int tile; - if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) { + if (this->aics[aicIndex].OuterPatrolGroupsMove == 0) { + int buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { int buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); if (buildingID != 0) { tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX @@ -62,15 +71,6 @@ namespace AI { .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] .addXgetTile; } - } else { - int buildingID - = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); - if (buildingID == 0) - continue; - tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX - + DAT_ViewportRenderState::instance - .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] - .addXgetTile; } if (tile == 0) continue; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 186d08c56..6db30bfbc 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35499,7 +35499,7 @@ SHC_3BB0A8C1_0x004D2830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D28B0 | 86.3% | select call block layout diffe +SHC_3BB0A8C1_0x004D28B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D2A70 | 54.3% | regalloc/spills differ From 84a65b058f9d9600a81a89e789fb95f5a3012e46 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:44 +0200 Subject: [PATCH 07/23] reimplement: SHC_3BB0A8C1_0x004CDB20 65.96% Loop register allocation --- .../chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp | 7 ++++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp index 94ed39aaf..d47f67184 100644 --- a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -11,11 +11,12 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CDB20 int AICState::chooseRandomBuildingIDFromPlayersArrayOfBuildings(int playerID) { + int rng = SEC_RNG::instance.currentNumber2 & 7; int chosenBuildingID = 0; int chosenPriority = 46; + short* buildings = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings; for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { - int rng = SEC_RNG::instance.currentNumber2 & 7; - int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + int buildingID = *buildings++; if (buildingID == 0) continue; @@ -34,8 +35,8 @@ namespace AI { continue; } - chosenBuildingID = buildingID; chosenPriority = j; + chosenBuildingID = buildingID; } } return chosenBuildingID; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6db30bfbc..b76f9c5bb 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35287,7 +35287,7 @@ SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 44.4% | regalloc/spill differs +SHC_3BB0A8C1_0x004CDB20 | 66.0% | Loop register allocation SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented From 177b341b060491acbc60b3284c5d6c930fd3a1ab Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:44 +0200 Subject: [PATCH 08/23] reimplement: SHC_3BB0A8C1_0x004CBA50 90.32% Flag store order/slots --- .../AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp index ccf876a64..ae59557d7 100644 --- a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp +++ b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp @@ -22,8 +22,8 @@ namespace AI { return; } - int allowIronMines = 1; int allowArmourers = 1; + int allowIronMines = 1; int allowFletchers = 1; int allowPitchRigs = 1; int allowWoodcutters = 1; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b76f9c5bb..2b21d625e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34853,7 +34853,7 @@ SHC_3BB0A8C1_0x004CB940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 90.7% | flag slot order differs +SHC_3BB0A8C1_0x004CBA50 | 90.3% | Flag store order/slots SHC_3BB0A8C1_0x004CBE30 | 100.0% | Reimplemented From 34a0b9a6493c5ab9f0b20048410a700abece443e Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:45 +0200 Subject: [PATCH 09/23] reimplement: SHC_3BB0A8C1_0x004CD2A0 73.17% tribeID/unitID regs swapped --- .../AI/AICState/recruitHarrassingSiegeEngines.cpp | 11 +++++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp index d72a3a55f..e1a4093f3 100644 --- a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -70,10 +70,13 @@ namespace AI { } int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165]; - if (tribeID == 0 - || DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] - || DAT_TribesState::instance.tribes[tribeID].size < 2) { + int hasEngineers = 0; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + && DAT_TribesState::instance.tribes[tribeID].size >= 2) + hasEngineers = 1; + if (!hasEngineers) { // Not enough engineers yet: recruit one at the engineers guild if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0 || DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 2b21d625e..27fe1d13f 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35273,7 +35273,7 @@ SHC_3BB0A8C1_0x004CD160 | 91.7% | Register allocation differs SHC_3BB0A8C1_0x004CD250 | 94.7% | Reimplemented -SHC_3BB0A8C1_0x004CD2A0 | 59.60% | Reimplemented functionally, code block ordering problem +SHC_3BB0A8C1_0x004CD2A0 | 73.2% | tribeID/unitID regs swapped SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented From 237721bc568867ecab168fb2d15ed6a17514a071 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:45 +0200 Subject: [PATCH 10/23] reimplement: SHC_3BB0A8C1_0x004D4340 77.19% playerID not kept in ebp --- .../AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp | 3 ++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp index 0296967f4..adeaeb3e4 100644 --- a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -37,6 +37,7 @@ namespace AI { OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); for (int i = 0; i < 3; i++) { + int j = 0; int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount [DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType]; int groups = count; @@ -52,7 +53,7 @@ namespace AI { stepSize = 1; } - for (int j = 0, patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { + for (int patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { int tribeID = DAT_GameState::instance.playerDataArray[playerID] .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 27fe1d13f..c707be87b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35539,7 +35539,7 @@ SHC_3BB0A8C1_0x004D4130 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4220 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4340 | 54.63% | Reimplemented, different register usage +SHC_3BB0A8C1_0x004D4340 | 77.2% | playerID not kept in ebp SHC_3BB0A8C1_0x004D4400 | 0.0% | Pending From 818c2ce86c78885758a8eb2e8656a61b24b3d0ff Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:46 +0200 Subject: [PATCH 11/23] reimplement: SHC_3BB0A8C1_0x004CC990 96.15% Init store before guard --- src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp | 12 ++++++------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp index ce194bb2d..2810bdd37 100644 --- a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -12,20 +12,20 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CC990 int AICState::smallestTribeOfUnitType(int playerID, AIVUnitType unitType) { + int smallestTribeID = 0; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { return 0; } - int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; - int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; - int smallestTribeID = 0; + int i = 0; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; int smallestIndex = 0; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; int smallestSize = 1000; - if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { - count = maxGroups; + if (count > this->aics[aicIndex].defWallPatrolGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = this->aics[aicIndex].defWallPatrolGroups; } - int i = 0; int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; for (;; i++) { if (i >= count) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c707be87b..421a50705 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34887,7 +34887,7 @@ SHC_3BB0A8C1_0x004CC8D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented -SHC_3BB0A8C1_0x004CC990 | 56.6% | regalloc/locals differ +SHC_3BB0A8C1_0x004CC990 | 96.2% | Init store before guard SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented From 0253742da43483e50fed270242fc0aca54c1a7e5 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 02:56:46 +0200 Subject: [PATCH 12/23] reimplement: SHC_3BB0A8C1_0x004D49E0 81.73% Pools 1 instead of 0 in ebp --- .../AI/AICState/updateAIPlayerState.cpp | 30 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp index 5050ed89e..a538147ca 100644 --- a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp +++ b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp @@ -38,26 +38,26 @@ namespace AI { int _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; if (_aiPlayerState == 0) { - if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( playerID)) { + // proceed with attack + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); + // fixme: struct layout. This looks like an int[10][8] attack tracker starting at + // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). + (&DAT_GameState::instance + .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .attackedByPlayerArrayUnk)[playerID * 8]++; + } else { // send own units back to keep or campfire MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRetreatUnits, this)(playerID); return; } - - // proceed with attack - DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; - DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; - DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; - DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); - // fixme: struct layout. This looks like an int[10][8] attack tracker starting at - // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). - (&DAT_GameState::instance - .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] - .attackedByPlayerArrayUnk)[playerID * 8]++; } else if ((MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID) && _aiPlayerState != 9) || (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && _aiPlayerState <= 5) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 421a50705..3b52f4cd1 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35559,7 +35559,7 @@ SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 52.84% | Register allocation differences, logic checks out +SHC_3BB0A8C1_0x004D49E0 | 81.7% | Pools 1 instead of 0 in ebp SHC_3BB0A8C1_0x004D5010 | 100.0% | Reimplemented From 6a93c2c25b69523a183d2127182d1b783519df44 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 03:55:33 +0200 Subject: [PATCH 13/23] reimplement: SHC_3BB0A8C1_0x004D1A00 68.80% targetPlayer/zero regs swapped --- .../AICState/decideOnTribeAttackLocation.cpp | 45 ++++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp index 48f809905..689d15b5a 100644 --- a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -44,8 +44,9 @@ namespace AI { int y = 0; int unitID = MACRO_CALL_MEMBER( OpenSHC::Map::Units::TribesState_Func::getUnitIDForIndexInTribe, DAT_TribesState::ptr)(tribeID, 0); + // the callee returns a bool in al; the generated header declares BOOLEnum if (unitID != 0 - && MACRO_CALL_MEMBER( + && (byte)MACRO_CALL_MEMBER( OpenSHC::Map::Navigation::PathFindingState_Func::pathFindingToAttackCastleIncludingMoat, DAT_PathFindingState::ptr)(DAT_TribesState::instance.tribes[tribeID].owner, targetPlayer, DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y, &moatWasHit, &x, @@ -108,31 +109,33 @@ namespace AI { int buildingID = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, DAT_GameState::ptr)(targetPlayer); int destinationTile; - if (buildingID == 0) { + if (buildingID != 0) { + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, + DAT_PathFindingState::ptr)(tribeID, buildingID, 1, + (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile], + targetPlayer); + destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; + if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) + destinationTile = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer] + .campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } else { + destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + } else { destinationTile = DAT_ViewportRenderState::instance .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] .addXgetTile + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; - } else if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { - destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX - + DAT_ViewportRenderState::instance - .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] - .addXgetTile; - } else { - MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, - DAT_PathFindingState::ptr)(tribeID, buildingID, 1, - (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance - .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] - .tile], - targetPlayer); - destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; - if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) - destinationTile - = DAT_ViewportRenderState::instance - .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] - .addXgetTile - + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; } if (destinationTile == 0) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 3b52f4cd1..2c68a6345 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35473,7 +35473,7 @@ SHC_3BB0A8C1_0x004D18B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs +SHC_3BB0A8C1_0x004D1A00 | 68.8% | targetPlayer/zero regs swapped SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation From 910a28b9e0fd8ff4325cb5e51ce86a33459f967b Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 03:55:33 +0200 Subject: [PATCH 14/23] reimplement: SHC_3BB0A8C1_0x004D4680 100.00% Reimplemented --- src/OpenSHC/AI/AICState/selectAttackTarget.cpp | 9 +++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp index cacb4823f..604d40c0a 100644 --- a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -25,18 +25,18 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D4680 void AICState::selectAttackTarget(int playerID) { - int selectedPlayerID = 0; int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == OpenSHC::AI::AIT_NULL) { return; } + int selectedPlayerID = 0; int aicIndex = aiType - 1; int fromX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; - int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; int lowestDistance = 10000; int highestGold = 0; int lowestPoints = 1000000; + int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeNervousness, this)(playerID); @@ -56,12 +56,13 @@ namespace AI { if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) { if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk) - != 0) { + == 0) { + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } else { DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; return; } - DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; } for (int i = 1; i < 9; i++) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 2c68a6345..634f5ea72 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35551,7 +35551,7 @@ SHC_3BB0A8C1_0x004D45E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4630 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4680 | 64.80% | Stuck, register usage +SHC_3BB0A8C1_0x004D4680 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending From 5eb682129f667c52e2208b2a6b72b2b071bdd2f7 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 04:26:35 +0200 Subject: [PATCH 15/23] reimplement: SHC_3BB0A8C1_0x004D2A70 58.57% Tile copies merged differently --- .../AI/AICState/aiGiveRaidInstructions.cpp | 55 +++++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp index 113ea823f..6f3e3727d 100644 --- a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -29,8 +29,7 @@ namespace AI { >= this->aics[aicIndex].RaidRetargetDelay) DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter = 0; - int totalRaidingTroopsUnk = DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk; - if (totalRaidingTroopsUnk == 0) + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk == 0) DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown > 0) DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown--; @@ -45,21 +44,27 @@ namespace AI { continue; int buildingID = 0; - int playerID_askerUnk = DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk; + int tile; if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( - playerID_askerUnk) + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) != 0) { if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) continue; buildingID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, - this)(playerID_askerUnk, i); + this)(DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk, i); if (buildingID == 0) buildingID = MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; } else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown == 0 - && totalRaidingTroopsUnk < MACRO_CALL_MEMBER( + && DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk < MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) { if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) continue; @@ -71,6 +76,12 @@ namespace AI { if (buildingID == 0) buildingID = MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; } else if (DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 182 || DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 181) { if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) @@ -86,6 +97,12 @@ namespace AI { buildingID = MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; } else { if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( tribeID, i, FALSE) @@ -109,27 +126,21 @@ namespace AI { targetBuildingID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); - if (targetBuildingID == 0) { - DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; - DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + if (targetBuildingID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, + DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; continue; } - MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( - tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, - DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); - DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; - DAT_TribesState::instance.tribes[tribeID].targetBuildingUID - = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; - DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; continue; } - if (buildingID == 0) - continue; - int tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX - + DAT_ViewportRenderState::instance - .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] - .addXgetTile; if (tile == 0) continue; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 634f5ea72..c739bc219 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35501,7 +35501,7 @@ SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D28B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2A70 | 54.3% | regalloc/spills differ +SHC_3BB0A8C1_0x004D2A70 | 58.6% | Tile copies merged differently SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented From 8a1fe3762c709979d8cc92de44e69ba0b2dd536c Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 04:26:35 +0200 Subject: [PATCH 16/23] reimplement: SHC_3BB0A8C1_0x004CE200 100.00% Reimplemented --- .../AICState/computeEnemyKeepApproachTile.cpp | 30 ++++++++----------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp index 375733aef..e1d61a345 100644 --- a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -20,6 +20,7 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 void AICState::computeEnemyKeepApproachTile(int playerID) { + int keepX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; int keepY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; this->aiBorderTilesIndex = 0; MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( @@ -29,8 +30,7 @@ namespace AI { for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { int tile = this->aiBorderTiles[i].tile; MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, - DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, - keepY, + DAT_DirectionAlgorithmState::ptr)(keepX, keepY, tile - DAT_ViewportRenderState::instance .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] @@ -43,9 +43,10 @@ namespace AI { int shortestDistanceTile = 0; int shortestDistance = 100000; for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { - if (this->aiBorderTiles[i].distance < shortestDistance) { + int distance = this->aiBorderTiles[i].distance; + if (distance < shortestDistance) { shortestDistanceTile = this->aiBorderTiles[i].tile; - shortestDistance = this->aiBorderTiles[i].distance; + shortestDistance = distance; } } @@ -53,7 +54,7 @@ namespace AI { if (shortestDistanceTile <= 0) return; - short y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY = y; DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = shortestDistanceTile; int x = shortestDistanceTile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; @@ -62,18 +63,13 @@ namespace AI { DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY; - if ((short)DAT_TileMapState::instance.OrganismLayer - [DAT_ViewportRenderState::instance - .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] - .addXgetTile - + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX] - != 0) - MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)( - (short)DAT_TileMapState::instance.OrganismLayer - [DAT_ViewportRenderState::instance - .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] - .addXgetTile - + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]); + int treeID = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (treeID != 0) + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(treeID); MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, DAT_PathFindingState::ptr)(DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x, DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y, -1, -1, 2000, FALSE); diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c739bc219..9fab13507 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35307,7 +35307,7 @@ SHC_3BB0A8C1_0x004CE110 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE1A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE200 | 64.5% | spilled local differs +SHC_3BB0A8C1_0x004CE200 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE270 | 0.0% | Pending From 4ce195cbdf7dca394556a8e116bb38800de6aff5 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 06:37:36 +0200 Subject: [PATCH 17/23] reimplement: SHC_3BB0A8C1_0x004D2000 97.54% Register naming, alignment pad --- .../AI/AICState/aiCreateSiegeUnits.cpp | 209 ++++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 112 insertions(+), 99 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index 403a5138e..a09756777 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -44,69 +44,81 @@ namespace AI { return; for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { + int siegeUnitType = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; + int i = 0; int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; if (locationCount > 3) locationCount = 3; + else if (locationCount <= 0) + continue; - for (int i = 0; i < locationCount; i++) { + while (i < locationCount) { + int index = i; + i++; int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots - [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot][i]; + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot][index]; if (location <= 0) continue; + int building = (short)DAT_TileMapState::instance.BuildingLayer[location]; int unitID = DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] .unitID; - int uid - = DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid; + int uid = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .uid; int buildingID = DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] .buildingID; if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID - = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay - = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].uid = 0; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] + .delay = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unknown = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] .buildingID = 0; continue; } if (buildingID != 0 && DAT_BuildingsState::instance.buildings[buildingID].uid != uid) { - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID - = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay - = 0; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].uid = 0; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] + .delay = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unknown = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] .buildingID = 0; continue; } if (unitID == 0 && buildingID == 0) { - if (((short)DAT_TileMapState::instance.BuildingLayer[location]) == 0) { - if (siegeIndex < 2) - continue; - } else { + if (building != 0) { if (siegeIndex >= 2) continue; - if (DAT_BuildingsState::instance - .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] - .buildingType + if (DAT_BuildingsState::instance.buildings[building].buildingType != OpenSHC::Map::Buildings::BT_TOWER4 - && DAT_BuildingsState::instance - .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] - .buildingType + && DAT_BuildingsState::instance.buildings[building].buildingType != OpenSHC::Map::Buildings::BT_TOWER5) continue; + } else { + if (siegeIndex < 2) + continue; } int requiredGold @@ -114,13 +126,15 @@ namespace AI { .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] .buildingType] .requiredGold; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; - if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] - + this->aics[aicIndex].defSiegeEngineGoldThreshold - < requiredGold) + if (requiredGold + > DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + + this->aics[aicIndex].defSiegeEngineGoldThreshold) continue; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .delay++; if (DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] .delay < this->aics[aicIndex].defSiegeEngineBuildDelay) continue; @@ -130,18 +144,15 @@ namespace AI { if (siegeIndex < 2) { int spawnedUnitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::spawnUnit, DAT_UnitsState::ptr)(playerID, playerID, x * 8 + 4, y * 8 + 4, - DAT_BuildingsState::instance - .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] - .terrainHeightUnk, - ((UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] - .unitType)); + DAT_BuildingsState::instance.buildings[building].terrainHeightUnk, (UnitType)siegeUnitType); DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] .unitID = spawnedUnitID; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid - = DAT_UnitsState::instance.units[spawnedUnitID].uid; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] + .uid = DAT_UnitsState::instance.units[spawnedUnitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] .buildingID = 0; MACRO_CALL_MEMBER( OpenSHC::Map::Buildings::BuildingsState_Func::processPlacementResourceLossForBuildingType, @@ -158,74 +169,76 @@ namespace AI { if (DAT_TileMapState::instance.buildingPlacementFail == FALSE) { DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] -= requiredGold; + int placedBuildingID = DAT_TileMapState::instance.placedBuildingID; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] + .aiSiegeCreationInformation[siegeIndex][index] .unitID = 0; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] - .uid - = DAT_BuildingsState::instance.buildings[DAT_TileMapState::instance.placedBuildingID] - .uid; + .aiSiegeCreationInformation[siegeIndex][index] + .uid = DAT_BuildingsState::instance.buildings[placedBuildingID].uid; DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] - .buildingID = DAT_TileMapState::instance.placedBuildingID; + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID = placedBuildingID; } } - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay - = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .delay = 0; return; } - if (unitID != 0 && buildingID == 0) { - int remainingEngineers - = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, - DAT_UnitsState::ptr)(unitID); - if (remainingEngineers <= 0) - continue; - if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { - DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + if (buildingID != 0) { + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount == requiredEngineers) continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated >= requiredEngineers) { + int tribeID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, + DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; } - int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, - this)(playerID, remainingEngineers); - int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; - if (targetUnitID == 0) - continue; - if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: - calculateCanPlayerUnitsNavigateToAreaFromArea, - DAT_PathFindingState::ptr)(playerID, - (short)DAT_TileMapState::instance - .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], - (short)DAT_TileMapState::instance - .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], - 0) - == 0) - continue; - DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; - DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; - MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( - tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, - DAT_UnitsState::instance.units[unitID].uid, 0); - return; + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; } - int requiredEngineers = MACRO_CALL_MEMBER( - OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, - DAT_BuildingsState::ptr)(buildingID); - if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount == requiredEngineers) + int remainingEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, DAT_UnitsState::ptr)(unitID); + if (remainingEngineers <= 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; continue; - if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated >= requiredEngineers) { - int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, - this)(playerID, requiredEngineers); - DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; - DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType - = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; - MACRO_CALL_MEMBER( - OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, - OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, - DAT_BuildingsState::instance.buildings[buildingID].uid, 0); - return; } - DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, remainingEngineers); + int unitArea + = (short) + DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile]; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + continue; + if (MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, unitArea, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + 0) + == 0) + continue; + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + return; } } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 9fab13507..7cbb25866 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 41.9% | regalloc/frame differ +SHC_3BB0A8C1_0x004D2000 | 97.5% | Register naming, alignment pad SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From bd9a65eafb990732bfc17d4e39d682648ee59900 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 06:37:36 +0200 Subject: [PATCH 18/23] reimplement: SHC_3BB0A8C1_0x004D3AE0 47.27% Loop register allocation --- src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 47 +++++++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp index 2f59cee0d..ca7b9558f 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -31,7 +31,7 @@ namespace AI { if (aiType == OpenSHC::AI::AIT_NULL) return; int aicIndex = aiType - 1; - + int recruitDiggingUnit = 0; bool isExtremeTrail = false; if (DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER && DAT_GameCore::instance.isSkirmishTrail == TRUE @@ -78,7 +78,6 @@ namespace AI { return; } - int recruitDiggingUnit = 0; if (this->aics[aicIndex].DefDiggingUnitMax != 0 && MACRO_CALL_MEMBER( OpenSHC::Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)(playerID) @@ -98,13 +97,15 @@ namespace AI { recruitDiggingUnit = 0; if (recruitDiggingUnit) { - if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 - && DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id == 0) + if (DAT_GameState::instance.playerDataArray[playerID].barracks.id != 0) + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + else if (DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id != 0) + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + else return; - unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_DEFENSIVE) { - if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex > 7) + if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex >= 8) DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; if ((&this->aics[aicIndex] .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] @@ -116,7 +117,7 @@ namespace AI { = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + 1; } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_RAIDING) { - if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) + if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex >= 8) DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; if ((&this->aics[aicIndex] .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] @@ -155,27 +156,37 @@ namespace AI { return; int unitID; - if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER - || unitType == OpenSHC::Map::Units::UT_TUNNELER || unitType == OpenSHC::Map::Units::UT_E_MONK - || unitType >= 70) + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER) unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( unitType, buildingID, playerID, 0); - else + else if (unitType == OpenSHC::Map::Units::UT_E_LADDER) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else if (unitType == OpenSHC::Map::Units::UT_E_MONK) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else if (unitType < 70) unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); if (unitID == 0) { if (this->aics[aicIndex].tradeAmountEquipment <= 0) return; if (DAT_UnitsState::instance.euroUnitAcquisitionFailReason != 2) return; - if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) DAT_GameState::instance.playerDataArray[playerID] - .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] - = this->aics[aicIndex].tradeAmountEquipment; + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; else DAT_GameState::instance.playerDataArray[playerID] - .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; return; } @@ -184,10 +195,10 @@ namespace AI { else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_DEFENSIVE) { if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk - < this->aics[aicIndex].DefWalls) - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); - else + >= this->aics[aicIndex].DefWalls) MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToSmallestPatrolTribe, this)(unitID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_RAIDING) { MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignUnitToDefensiveTribe, this)(unitID); diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7cbb25866..e4ed927cb 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35527,7 +35527,7 @@ SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 43.4% | regalloc/spills differ +SHC_3BB0A8C1_0x004D3AE0 | 47.3% | Loop register allocation SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented From 2d0d4e52f79ffe07e44becf03a80e526e98f3225 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 08:27:45 +0200 Subject: [PATCH 19/23] reimplement: SHC_3BB0A8C1_0x004D2000 98.46% Register naming, alignment pad --- src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index a09756777..2b0fa6b11 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -45,9 +45,9 @@ namespace AI { for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { int siegeUnitType = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; - int i = 0; int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; + int i = 0; if (locationCount > 3) locationCount = 3; else if (locationCount <= 0) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index e4ed927cb..665b7f78d 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 97.5% | Register naming, alignment pad +SHC_3BB0A8C1_0x004D2000 | 98.5% | Register naming, alignment pad SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From 0cb54ab99f08762041684c10aa545f4a0c0f57df Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 09:19:08 +0200 Subject: [PATCH 20/23] reimplement: SHC_3BB0A8C1_0x004D2000 99.69% Loop latch reload order --- src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp | 7 +++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index 2b0fa6b11..830649dbe 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -225,12 +225,11 @@ namespace AI { int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; if (targetUnitID == 0) continue; + int targetArea = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile]; if (MACRO_CALL_MEMBER( OpenSHC::Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, - DAT_PathFindingState::ptr)(playerID, unitArea, - (short)DAT_TileMapState::instance - .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], - 0) + DAT_PathFindingState::ptr)(playerID, unitArea, targetArea, 0) == 0) continue; DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 665b7f78d..2cda0bb9c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 98.5% | Register naming, alignment pad +SHC_3BB0A8C1_0x004D2000 | 99.7% | Loop latch reload order SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From e4f2b69a5c39b412e644ecd9cbe4278f3f093304 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 09:25:03 +0200 Subject: [PATCH 21/23] reimplement: SHC_3BB0A8C1_0x004D48F0 100.00% Reimplemented aiBuyAndSellGoods only differs in its tail-jump targets; with the sellExcessGoods and buyRequiredGoods resolvers enabled it matches 100%. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JP48NBCkB9vy6M7zLArtgo --- status/addresses-SHC-3BB0A8C1.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 2cda0bb9c..c8e8dbbf7 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35555,7 +35555,7 @@ SHC_3BB0A8C1_0x004D4680 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending -SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs +SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented From a229c215815617d70fe20caed0ccd65b3f6d5226 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 09:25:29 +0200 Subject: [PATCH 22/23] status: refresh three AICState entries measured with data resolvers enabled computeAttackPossibilities, playNervous2BikFromPlayer and buyRequiredGoods were recorded before the global data resolvers were switched on. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JP48NBCkB9vy6M7zLArtgo --- status/addresses-SHC-3BB0A8C1.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c8e8dbbf7..f54c0cfcb 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35421,7 +35421,7 @@ SHC_3BB0A8C1_0x004D0A20 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0A80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0B10 | 68.0% | Load scheduling differs +SHC_3BB0A8C1_0x004D0B10 | 76.0% | aiType-1 load sunk past branch SHC_3BB0A8C1_0x004D0B70 | 100.0% | Reimplemented @@ -35505,7 +35505,7 @@ SHC_3BB0A8C1_0x004D2A70 | 58.6% | Tile copies merged differently SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs +SHC_3BB0A8C1_0x004D2EC0 | 89.7% | Address computed after 1st call SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented @@ -35523,7 +35523,7 @@ SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3960 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented +SHC_3BB0A8C1_0x004D39B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending From 6eb9b8236e496de29d421b18db720ad70c936d2e Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 16 Oct 2026 10:29:27 +0200 Subject: [PATCH 23/23] reimplement: SHC_3BB0A8C1_0x004CDCF0 83.25% Pools 1 in a register --- .../AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp | 5 ++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp index 5f0124873..fa5a02e7f 100644 --- a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -65,9 +65,8 @@ namespace AI { unreadyTeamMembers++; } - if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel) - return TRUE; - if (unreadyTeamMembers == 0) + if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel + || unreadyTeamMembers == 0) return TRUE; DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience++; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f54c0cfcb..a39366f16 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35293,7 +35293,7 @@ SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 77.2% | regalloc/counters differ +SHC_3BB0A8C1_0x004CDCF0 | 83.2% | Pools 1 in a register SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented