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..fc7589ea6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -0,0 +1,81 @@ +#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 firstSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + int selectedTribeID = 0; + int selectedSlot = 0; + int smallestSize = 1000; + int groupsCount; + switch (firstSlot) { + 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; + } + if (groupsCount < 1) + groupsCount = 1; + + 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[firstSlot]) + return tribeID; + selectedTribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = firstSlot; + } else { + 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 + != 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..1f7f758d7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -0,0 +1,65 @@ +#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)) { + 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/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..830649dbe --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -0,0 +1,246 @@ +#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 siegeUnitType = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; + 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) + continue; + + while (i < locationCount) { + int index = i; + i++; + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots + [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][index] + .unitID; + int uid = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .uid; + int buildingID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID; + + if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { + 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][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][index] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].uid + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .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 (building != 0) { + if (siegeIndex >= 2) + continue; + if (DAT_BuildingsState::instance.buildings[building].buildingType + != OpenSHC::Map::Buildings::BT_TOWER4 + && DAT_BuildingsState::instance.buildings[building].buildingType + != OpenSHC::Map::Buildings::BT_TOWER5) + continue; + } else { + if (siegeIndex < 2) + continue; + } + + int requiredGold + = DAT_BuildingsState::instance + .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType] + .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][index] + .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[building].terrainHeightUnk, (UnitType)siegeUnitType); + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unitID = spawnedUnitID; + DAT_GameState::instance.playerDataArray[playerID] + .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, + 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; + int placedBuildingID = DAT_TileMapState::instance.placedBuildingID; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .uid = DAT_BuildingsState::instance.buildings[placedBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID = placedBuildingID; + } + } + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .delay = 0; + return; + } + 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; + } + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + + 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 unitArea + = (short) + DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile]; + 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, targetArea, 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/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp new file mode 100644 index 000000000..aab39a342 --- /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::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 + + 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; + } + } + 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..6f3e3727d --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -0,0 +1,158 @@ +#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; + + 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--; + + 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 tile; + if (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) + != 0) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + 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 + && 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; + 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); + 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) + 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); + 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) + != 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) { + 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; + } + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + continue; + } + + 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..ca7b9558f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -0,0 +1,212 @@ +#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; + int recruitDiggingUnit = 0; + 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; + } + + 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) + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + else if (DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id != 0) + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + else + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + 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] + == 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 >= 8) + 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) + 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_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) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + else + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + 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::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); + } 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..fa5a02e7f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -0,0 +1,80 @@ +#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 + || 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..7f4810a3e --- /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(int 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..2356560a1 --- /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(int 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..468d283d2 --- /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(int 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..d47f67184 --- /dev/null +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -0,0 +1,46 @@ +#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 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 buildingID = *buildings++; + 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; + } + + chosenPriority = j; + chosenBuildingID = buildingID; + } + } + 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..e1d61a345 --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -0,0 +1,81 @@ +#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 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)( + 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)(keepX, 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++) { + int distance = this->aiBorderTiles[i].distance; + if (distance < shortestDistance) { + shortestDistanceTile = this->aiBorderTiles[i].tile; + shortestDistance = distance; + } + } + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = 0; + if (shortestDistanceTile <= 0) + return; + + 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; + 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; + + 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); + 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..689d15b5a --- /dev/null +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -0,0 +1,157 @@ +#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); + // the callee returns a bool in al; the generated header declares BOOLEnum + if (unitID != 0 + && (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, + &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) { + 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; + } + + 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..ae59557d7 --- /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 allowArmourers = 1; + int allowIronMines = 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..e1a4093f3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -0,0 +1,165 @@ +#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]; + 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] + < 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..604d40c0a --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -0,0 +1,144 @@ +#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 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 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); + + // 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].requestStateUnk = 0; + } else { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; + return; + } + } + + 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..adeaeb3e4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -0,0 +1,95 @@ +#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 j = 0; + 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 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..2810bdd37 --- /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) + { + int smallestTribeID = 0; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return 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 > this->aics[aicIndex].defWallPatrolGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = this->aics[aicIndex].defWallPatrolGroups; + } + + 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..a538147ca --- /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)) { + // 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; + } + } 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; + } + } +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f6e47278b..a39366f16 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.3% | Flag store order/slots -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 | 96.2% | Init store before guard -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 | 80.1% | Register allocation differs 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 | 73.2% | tribeID/unitID regs swapped -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 | 66.0% | Loop register allocation -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 | 83.2% | Pools 1 in a register -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 | 100.0% | Reimplemented 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 | 100.0% | Reimplemented -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 | 76.0% | aiType-1 load sunk past branch -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 | 68.8% | targetPlayer/zero regs swapped -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 | 99.7% | Loop latch reload order 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 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2A70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2A70 | 58.6% | Tile copies merged differently -SHC_3BB0A8C1_0x004D2E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2EC0 | 89.7% | Address computed after 1st call -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 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3AE0 | 47.3% | Loop register allocation -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 | 77.2% | playerID not kept in ebp 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 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4940 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D49E0 | 81.7% | Pools 1 instead of 0 in ebp 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