diff --git a/src/OpenSHC/AI/AICState/Constructor_AICState.cpp b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp new file mode 100644 index 000000000..2eedf70e1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp @@ -0,0 +1,14 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3940 + AICState* AICState::Constructor_AICState() + { + MACRO_CALL_MEMBER(AICState_Func::setInitialAIC, this)(); + this->aiTauntResponseTimeWindow = 0; + return this; + } +} +} diff --git a/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp new file mode 100644 index 000000000..bdd9f7312 --- /dev/null +++ b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp @@ -0,0 +1,23 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D45E0 + void AICState::abortAttackAndGoIdle(int playerID) + { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp new file mode 100644 index 000000000..bd48cbe1c --- /dev/null +++ b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp @@ -0,0 +1,30 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDA50 + void AICState::addBuildingToTargetableBuildings(int buildingID) + { + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return; + if (DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker >= 100) + return; + if (DAT_BuildingsState::instance.buildings[buildingID].fireDuration != 0) + return; + + DAT_GameState::instance.playerDataArray[owner] + .top100TargetableBuildings[DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker] + = buildingID; + DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker++; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp new file mode 100644 index 000000000..3aae5e97a --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp @@ -0,0 +1,44 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.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 0x004CC680 + int AICState::addEngineersToSelection(int playerID, int unitCount) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (unitCount <= 0) + return tribeID; + 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; + if (DAT_UnitsState::instance.units[unitID].state.generic == Map::Units::States::US_STAND_UPUnk) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + unitCount--; + } + return tribeID; + } + +} +} 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..50144e366 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -0,0 +1,79 @@ +#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 tribeType = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + int groupsCount; + switch (tribeType) { + case 15: + groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; + break; + case 186: + groupsCount = this->aics[aicIndex].AttUnitBackupGroupsCount; + break; + case 190: + groupsCount = this->aics[aicIndex].AttUnitSiegeDefGroupsCount; + break; + case 192: + groupsCount = this->aics[aicIndex].AttMainGroupsCount; + break; + default: + groupsCount = 1; + break; + } + + int selectedTribeID = 0; + int selectedSlot = 0; + if (groupsCount <= 1) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeType]) + return tribeID; + selectedTribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = tribeType; + } else { + int smallestSize = 1000; + for (int slot = tribeType; slot < tribeType + 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/addUnitToSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp new file mode 100644 index 000000000..40602a5c4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.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 0x004D2730 + void AICState::addUnitToSmallestPatrolTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + int aiType = DAT_GameState::instance.playerDataArray[owner].aiType; + if (aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 4; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getSmallestPatrolTribe, this)( + owner, this->aics[aiType - 1].OuterPatrolGroupsCount); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} 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/aiChoiceAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp new file mode 100644 index 000000000..890450502 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp @@ -0,0 +1,45 @@ +#include "../AICState.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 0x004CEC80 + BOOLEnum AICState::aiChoiceAttForceRallyPercentage(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int activeTribes = 0; + int rallyingTribes = 0; + 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; + + activeTribes++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) + rallyingTribes++; + } + } + + if (activeTribes <= 0) + return FALSE; + return rallyingTribes * 100 / activeTribes >= this->aics[aicIndex].AttForceRallyPercentage; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp new file mode 100644 index 000000000..8032d413f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -0,0 +1,100 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFCD0 + void AICState::aiCommandSiegeEngineTribes(int playerID) + { + int shieldTargetTribeIDs[3]; + shieldTargetTribeIDs[0] = 0; + shieldTargetTribeIDs[1] = 0; + shieldTargetTribeIDs[2] = 0; + int targetCount = 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[186 + i]) + continue; + shieldTargetTribeIDs[targetCount] = tribeID; + targetCount++; + } + + int shieldCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (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) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER) { + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x414); + } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM) { + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.gateValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x3f6); + else if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x413); + } else { + if (shieldCount >= 3) + continue; + + int targetTribeID = shieldTargetTribeIDs[shieldCount]; + DAT_TribesState::instance.tribes[tribeID].field56_0x1f2 = targetTribeID; + DAT_TribesState::instance.tribes[tribeID].uid2 = DAT_TribesState::instance.tribes[targetTribeID].uid; + int targetUnitID = DAT_TribesState::instance.tribes[targetTribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[targetTribeID].tribeID = tribeID; + int tile = DAT_UnitsState::instance.units[targetUnitID].tile; + shieldCount++; + if (tile <= 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, Instructions::UMSE_0); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::calculatePreferredRelativeOrientation, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + DAT_TribesState::instance.tribes[tribeID].orientation + = DAT_DirectionAlgorithmState::instance.orientation; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp new file mode 100644 index 000000000..f4f46c614 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF470 + void AICState::aiCommandTribe11StandGroundToGate(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + if (MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTribeToNearbyGatehouse, DAT_TroopValueState::ptr)( + tribeID) + == 0) + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTowardsParticularUnits, DAT_TroopValueState::ptr)( + tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp new file mode 100644 index 000000000..b82c43b75 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF2E0 + void AICState::aiCommandTribe12AggressiveApproach(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) + 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); + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + 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)(attackedPlayerID, 200) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp new file mode 100644 index 000000000..b921b42ae --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp @@ -0,0 +1,32 @@ +#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 0x004CF270 + void AICState::aiCommandTribe13DefensiveWave(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[13]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[13]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f4); + } +} +} 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..7e51f30d5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -0,0 +1,258 @@ +#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) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + 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; + + // The original also processes index 4, one past the end of SiegeEngineMetaInfoArray. + for (int siegeIndex = 0; siegeIndex < 5; siegeIndex++) { + UnitType unitType + = (UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; + int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; + int locationCount + = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + if (locationCount >= 4) + locationCount = 3; + + for (int i = 0; i < locationCount; i++) { + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i]; + if (location <= 0) + continue; + + int existingBuildingID = (short)DAT_TileMapState::instance.BuildingLayer[location]; + int unitID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID; + int uid + = DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid; + int buildingID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID; + + if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + if (buildingID != 0 && DAT_BuildingsState::instance.buildings[buildingID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + + if (unitID == 0) { + 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 (requiredEngineers + > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + 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; + } + + if (existingBuildingID == 0) { + if (siegeIndex < 2) + continue; + } else if (siegeIndex < 2) { + if (DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + != OpenSHC::Map::Buildings::BT_TOWER4 + && DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + != OpenSHC::Map::Buildings::BT_TOWER5) + continue; + } + + int requiredGold + = DAT_BuildingsState::instance + .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType] + .requiredGold; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + + this->aics[aiType - 1].defSiegeEngineGoldThreshold + < requiredGold) + continue; + if (DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .delay + < this->aics[aiType - 1].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[existingBuildingID].terrainHeightUnk, unitType); + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = spawnedUnitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[spawnedUnitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::processPlacementResourceLossForBuildingType, + DAT_BuildingsState::ptr)(playerID, + (BuildingType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType, + 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)( + playerID, x, y, + (MappersEnum)(ushort)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .commandBuildingType, + 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail == FALSE) { + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= requiredGold; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .uid + = DAT_BuildingsState::instance.buildings[DAT_TileMapState::instance.placedBuildingID] + .uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = DAT_TileMapState::instance.placedBuildingID; + } + } + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + return; + } + + if (unitID != 0) { + 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 (requiredEngineers + > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, + DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + int remainingEngineers + = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, + DAT_UnitsState::ptr)(unitID); + if (remainingEngineers <= 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, remainingEngineers); + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + 0) + == 0) + continue; + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + return; + } + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiFarmChoice.cpp b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp new file mode 100644 index 000000000..46898001b --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB180 + int AICState::aiFarmChoice(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return (&this->aics[aiType - 1].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp new file mode 100644 index 000000000..1e8ad2f21 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -0,0 +1,77 @@ +#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 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 (int i = 0; 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 buildingID = 0; + if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) + buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); + int tile; + 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..2f1047fba --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -0,0 +1,144 @@ +#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 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 (int i = 0; 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; + 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); + } 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); + } else if (DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 182 + || DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 181) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + } else { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + targetBuildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + if (targetBuildingID == 0) { + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + continue; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, + DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + if (buildingID == 0) + continue; + int tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + == FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp new file mode 100644 index 000000000..b8de9aefa --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0580 + BOOLEnum AICState::aiOfTypeInCurrentGame(int aicIndex) + { + for (int playerID = 1; playerID < 9; playerID++) { + if (DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].aiType - 1 != aicIndex) + continue; + return TRUE; + } + return FALSE; + } + +} +} 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/aiRecruitEngineers.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp new file mode 100644 index 000000000..23c4bf441 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC520 + void AICState::aiRecruitEngineers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired == FALSE) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (this->aics[aiType - 1].recruitGoldThreshold + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < 30) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, guildID, playerID, 0); + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = FALSE; + } +} +} 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..0bcda0b49 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -0,0 +1,195 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3AE0 + void AICState::aiRecruitUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + bool isExtremeTrail = DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME; + + 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) { + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker < recruitInterval) + return; + } else { + recruitCount = 4; + } + + 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; + } + + bool recruitDiggingUnit = false; + 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 = true; + } + + UnitType unitType = (UnitType)0; + int behaviourType = 0; + for (int i = 0; i < recruitCount; i++) { + if (recruitDiggingUnit && (SEC_RNG::instance.currentNumber2 & 1) != 0) + recruitDiggingUnit = false; + + if (recruitDiggingUnit) { + if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id == 0) + return; + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + int choice = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex; + unitType = (UnitType)(&this->aics[aicIndex].DefUnit1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = choice + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + int choice = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex; + unitType = (UnitType)(&this->aics[aicIndex].RaidUnit1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = choice + 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 || unitType == OpenSHC::Map::Units::UT_E_LADDER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].tunnelersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_MONK) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_CATHEDRAL); + else if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER || unitType == OpenSHC::Map::Units::UT_E_MONK + || unitType >= 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment <= 0) + return; + if (DAT_UnitsState::instance.euroUnitAcquisitionFailReason != 2) + return; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + else + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + return; + } + + if (recruitDiggingUnit) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + < this->aics[aicIndex].DefWalls) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToSmallestPatrolTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignUnitToDefensiveTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToItsTribe, this)(unitID, behaviourType); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp new file mode 100644 index 000000000..1d2272d15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1950 + void AICState::aiRegisterTribeAndAssignTarget(int tribeID, int tribeUID) + { + if (DAT_TribesState::instance.tribes[tribeID].size > 0) { + if (DAT_GameState::instance.playerDataArray[DAT_TribesState::instance.tribes[tribeID].owner].aiType + == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 1000; i++) { + int existingTribeID = this->tribeIDArray[i]; + if (existingTribeID != 0 + && DAT_TribesState::instance.tribes[existingTribeID].uid == this->tribeUIDArray[i]) + continue; + + this->tribeIDArray[i] = tribeID; + this->tribeUIDArray[i] = tribeUID; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTargetForTribeBasedOnTargetChoiceLogic, this)( + DAT_TribesState::instance.tribes[tribeID].owner, tribeID); + return; + } + return; + } + DAT_TribesState::instance.tribes[tribeID].tribeState = 3; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp new file mode 100644 index 000000000..7117f018c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp @@ -0,0 +1,65 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.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 0x004CB3A0 + undefined4 AICState::aiRequiresExtraOxtethers(int playerID) + { + int quarryID + = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY); + int highestStonePerTether = 0; + int highestQuarryID = 0; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_OXTETHER, 1) + >= 10) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = 0; + if (quarryID == 0) + return 0; + + for (; quarryID != 0; + quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findNextBuildingForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY, quarryID)) { + int stone = DAT_BuildingsState::instance + .buildings[(short)DAT_BuildingsState::instance.buildings[quarryID].quarryStockpileID] + .resources[Game::Resources::RT_STONE]; + int tetherCount = 0; + for (int i = 0; i < 3; i++) { + int tetherID = DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i]; + if (tetherID == 0) + continue; + if (DAT_BuildingsState::instance.buildings[tetherID].buildingType + == OpenSHC::Map::Buildings::BT_OXTETHER + && DAT_BuildingsState::instance.buildings[tetherID].oxtetherLinkedQuarryID == quarryID) + tetherCount++; + else + DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i] = 0; + } + + if (tetherCount <= 0) { + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = quarryID; + return 1; + } + if (stone / tetherCount > highestStonePerTether) { + highestQuarryID = quarryID; + highestStonePerTether = stone / tetherCount; + } + } + + if (highestStonePerTether <= 20) + return 0; + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = highestQuarryID; + return 1; + } + +} +} 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..1004fb6c6 --- /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 aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + return TRUE; + + 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 (int otherPlayerID = 1; otherPlayerID < 9; otherPlayerID++) { + if (otherPlayerID == playerID) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + otherPlayerID) + == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::isAIPlayer, DAT_GameSynchronyState::ptr)( + otherPlayerID) + == FALSE) + continue; + if (DAT_GameState::instance.mapAndTime.playerTeams[otherPlayerID] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) + continue; + + if (DAT_GameState::instance.playerDataArray[otherPlayerID].aiPlayerState != 0) + return TRUE; + if (DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackingEngineerTroops + >= this->aics[DAT_GameState::instance.playerDataArray[otherPlayerID].aiType - 1].AttForceBase + + DAT_GameState::instance.playerDataArray[otherPlayerID].currentWaveRandomAttackingStrength) + readyTeamMembers++; + else + unreadyTeamMembers++; + } + + if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel) + return TRUE; + if (unreadyTeamMembers == 0) + return TRUE; + + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience > 48 + && DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_CAMPAIGN_MISSION) + return TRUE; + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp new file mode 100644 index 000000000..02ab1e06c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB220 + BOOLEnum AICState::aiShouldBuildFarm(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int farmCount + = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countFarms, DAT_BuildingsState::ptr)(playerID, 1); + if (farmCount <= 0) + return TRUE; + if (farmCount >= this->aics[aicIndex].maxFarms) + return FALSE; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / farmCount + >= this->aics[aicIndex].populationPerFarm; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp new file mode 100644 index 000000000..1f0c072e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB4D0 + uint AICState::aiShouldBuildIronMine(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerIronmine <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_IRONMINE, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxIronmines) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerIronmine; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp new file mode 100644 index 000000000..5cf71d7e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB540 + uint AICState::aiShouldBuildPitchrig(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerPitchrig <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_PITCHRIG, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxPitchrigs) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerPitchrig; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp new file mode 100644 index 000000000..2fcefd9ab --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB330 + uint AICState::aiShouldBuildQuarry(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerQuarry <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_QUARRY, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxQuarries) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerQuarry; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp new file mode 100644 index 000000000..7d223c5a9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB290 + BOOLEnum AICState::aiShouldBuildWoodcutter(int playerID) + { + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return FALSE; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerWoodcutter <= 0) + return FALSE; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT, 1); + if (count <= 0) { + count = 1; + } else { + if (count >= this->aics[aicIndex].maxWoodcutters) + return FALSE; + if (count >= 6 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 90) + return FALSE; + if (count >= 3 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 30) + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerWoodcutter; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp new file mode 100644 index 000000000..cd1b58035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp @@ -0,0 +1,91 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAEA0 + void AICState::aiUpdateTaxesAndRations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + int popularity = DAT_GameState::instance.playerDataArray[playerID].popularity; + if (popularity <= this->aics[aicIndex].criticalPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 2; + else if (popularity <= this->aics[aicIndex].lowestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 1; + else if (popularity >= this->aics[aicIndex].highestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 2) { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 2; + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 8) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 1) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > this->aics[aicIndex].taxesMin) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + else + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMin; + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < this->aics[aicIndex].taxesMax) { + int lowerTaxesLimit; + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 15) + lowerTaxesLimit = 6; + else if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 10) + lowerTaxesLimit = 5; + else + lowerTaxesLimit = 4; + + if (DAT_GameState::instance.playerDataArray[playerID].storedPopularityPercent <= popularity) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting++; + else if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > lowerTaxesLimit) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + } else { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMax; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + } else { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } + } + + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > 11) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 11; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting > 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp new file mode 100644 index 000000000..d6f813898 --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp @@ -0,0 +1,49 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/States/UnitState.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 0x004CC5A0 + int AICState::assignRequiredIdleEngineersToNewTribe(int playerID, int count) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (count <= 0) + return tribeID; + 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 == 22) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 10) + continue; + if (DAT_UnitsState::instance.units[unitID].engineerRelatedUnk != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].state.generic != 105 + && DAT_UnitsState::instance.units[unitID].state.generic != 2 + && DAT_UnitsState::instance.units[unitID].state.generic != Map::Units::States::US_IDLEUnk) + 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(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + count--; + } + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp new file mode 100644 index 000000000..9f320a41b --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp @@ -0,0 +1,40 @@ +#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 0x004D2660 + void AICState::assignUnitToATribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + AIVUnitType aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, + DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[owner].aivPauses[1] > 0 + ? 1 + : 0); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 1; + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlotLocationCount[aivUnitType] <= 0) { + if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) { + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 0)); + return; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 1)); + return; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::smallestTribeOfUnitType, this)(owner, aivUnitType)); + } +} +} 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..ffc6b981f --- /dev/null +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDB20 + int AICState::chooseRandomBuildingIDFromPlayersArrayOfBuildings(int playerID) + { + int chosenBuildingID = 0; + int chosenPriority = 46; + int buildingCount = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + int rng = SEC_RNG::instance.currentNumber2 & 7; + for (int i = 0; i < buildingCount; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) + continue; + + int buildingType = DAT_BuildingsState::instance.buildings[buildingID].buildingType; + for (int j = 0; j < chosenPriority; j++) { + bool isMatch; + if (rng <= 1) + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]; + else if (rng <= 3) + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]; + else + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]; + + if (isMatch) { + chosenBuildingID = buildingID; + chosenPriority = j; + } + } + } + return chosenBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp new file mode 100644 index 000000000..8e7e23178 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp @@ -0,0 +1,39 @@ +#include "../AICState.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 0x004CF870 + void AICState::clearOutdatedAITribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + if (i == 8) + continue; + + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + 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_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j] = 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType] = 0; + } + } + } + +} +} 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..d158bc23f --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 + void AICState::computeEnemyKeepApproachTile(int playerID) + { + int 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++) { + if (this->aiBorderTiles[i].distance < shortestDistance) { + shortestDistanceTile = this->aiBorderTiles[i].tile; + shortestDistance = this->aiBorderTiles[i].distance; + } + } + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = 0; + if (shortestDistanceTile <= 0) + return; + + short y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY = y; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = shortestDistanceTile; + int x = shortestDistanceTile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y + = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY; + + if ((short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX] + != 0) + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)( + (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x, + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y, -1, -1, 2000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::storeDestinationOptionsUnk, + DAT_PathFindingState::ptr)(playerID - 1); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeNervousness.cpp b/src/OpenSHC/AI/AICState/computeNervousness.cpp new file mode 100644 index 000000000..2d92d4bca --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeNervousness.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3780 + void AICState::computeNervousness(int playerID) + { + int troopValueNearKeep + = DAT_TroopValueState::instance.attackInfo.playerTotalTroopValueOfTroopsNearKeep[playerID]; + int enemyTroopValue = DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + + int margin; + if (enemyTroopValue < 50) + margin = -20; + else if (enemyTroopValue < 100) + margin = 0; + else if (enemyTroopValue < 200) + margin = 20; + else if (enemyTroopValue < 400) + margin = 100; + else if (enemyTroopValue < 600) + margin = 200; + else if (enemyTroopValue < 800) + margin = 300; + else + margin = 500; + + if (troopValueNearKeep > enemyTroopValue + margin) { + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 0; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID] + .totalEnemyTroopValueByPlayerID[DAT_GameSynchronyState::instance.currentPlayerSlotID] + > troopValueNearKeep) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous1BikFromPlayer, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playRequestHelpBikFromPlayer, this)(playerID); + } + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp new file mode 100644 index 000000000..4cad5f5ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp @@ -0,0 +1,36 @@ +#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 0x004CFBC0 + int AICState::countSiegeEngineUnits(int playerID) + { + int count = 0; + 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].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + 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_FBALLISTA) + count++; + } + return count; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp new file mode 100644 index 000000000..4395e208d --- /dev/null +++ b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp @@ -0,0 +1,26 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC910 + int AICState::createTribeForUnitType(int playerID, int unitSelectionTypeIndex) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp new file mode 100644 index 000000000..48f809905 --- /dev/null +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -0,0 +1,154 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1A00 + void AICState::decideOnTribeAttackLocation(uint tribeID) + { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) != FALSE) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + return; + } + + int targetPlayer = DAT_TribesState::instance.tribes[tribeID].targetPlayer_outpost; + if (DAT_TribesState::instance.tribes[tribeID].randomNumber == 0) { + DAT_TribesState::instance.tribes[tribeID].randomNumber = SEC_RNG::instance.currentNumber2; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + } + + DAT_TribesState::instance.tribes[tribeID].counter++; + if (DAT_TribesState::instance.tribes[tribeID].counter + == DAT_TribesState::instance.tribes[tribeID].randomNumber % 6 + 7) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + byte moatWasHit = 0; + int x = 0; + int y = 0; + int unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::getUnitIDForIndexInTribe, DAT_TribesState::ptr)(tribeID, 0); + if (unitID != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::pathFindingToAttackCastleIncludingMoat, + DAT_PathFindingState::ptr)(DAT_TribesState::instance.tribes[tribeID].owner, targetPlayer, + DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y, &moatWasHit, &x, + &y)) { + if (moatWasHit != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, (UnitInstructionType)35, + DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile + x, 0, 0); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_FILL_MOAT, x, y, 1000); + return; + } + } + + if (DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 182 + && DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 185) { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, FALSE) != FALSE) + return; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + return; + } + + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + if (buildingID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = buildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + return; + } + + int destinationX = DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + int destinationY = DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, + DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile + destinationX) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + return; + } + + int buildingID = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + int destinationTile; + if (buildingID == 0) { + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } else if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, + DAT_PathFindingState::ptr)(tribeID, buildingID, 1, + (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile], + targetPlayer); + destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; + if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } + + if (destinationTile == 0) + return; + + int destinationY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[destinationTile]; + int destinationX + = destinationTile - DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)( + tribeID, destinationTile) + == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, targetPlayer); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp new file mode 100644 index 000000000..a8c02b8b2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp @@ -0,0 +1,97 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Buildings/BuildingLogicalState.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0280 + void AICState::destroyAnyBuildingInCategory(int playerID, AINervousDestroyBuildingCategory buildingCategory) + { + for (int buildingID = 1; buildingID < DAT_BuildingsState::instance.maxBuildingsCount; buildingID++) { + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == 0) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == BLS_REMOVE) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].owner != playerID) + continue; + + bool matches = false; + if (buildingCategory == AINDBC_FEAR_FACTOR_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CESSPIT + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BURNINGSTAKE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GIBBET + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNGEON + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STRETCHINGRACK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_RACKFLOGGING + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHOPPINGBLOCK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNKINGSTOOL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DOGCAGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STATUE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_SHRINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DANCINGBEAR + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POND + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GALLOWS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STOCKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WITCHHOIST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MAYPOLE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GARDEN; + else if (buildingCategory == AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_FLETCHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BLACKSMITH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POLETURNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ARMOURER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TANNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_INN + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APOTHECARY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHAPEL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHURCH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CATHEDRAL; + else if (buildingCategory == AINDBC_GATEHOUSES_TOWERS_GUILDS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSELARGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSESMALL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DRAWBRIDGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER1 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER2 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER3 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER4 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER5 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ENGINEERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TUNNELERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OILSMELTER; + else if (buildingCategory == AINDBC_BARRACKS_MERCPOST_STABLES) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MERCENARYPOST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BARRACKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STABLES; + else if (buildingCategory == AINDBC_WATER_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WELL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WATERPOT; + else if (buildingCategory == AINDBC_RESOURCE_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OXTETHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_IRONMINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_PITCHRIG + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BAKERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BREWERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_QUARRY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MILL; + else if (buildingCategory == AINDBC_FARMS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WHEATFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_HOPFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APPLEFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DAIRYFARM; + + if (!matches) + continue; + + MACRO_CALL_MEMBER(BuildingsState_Func::giveBackResourceForDestroyedBuilding, DAT_BuildingsState::ptr)( + buildingID, playerID, 50); + MACRO_CALL_MEMBER(BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp new file mode 100644 index 000000000..e041584c2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp @@ -0,0 +1,44 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB940 + undefined4 AICState::destroyBuildingIfNoWorker(int buildingID) + { + short countdown = DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue; + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (countdown <= 0) + return 0; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[owner] != -1) + return 0; + if (DAT_GameSynchronyState::instance.currentAIArray[owner] == 0) + return 0; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return 0; + + if (DAT_BuildingsState::instance.buildings[buildingID].workers[0] != 0) { + if (DAT_GameState::instance.mapAndTime.monthTicks == owner) + DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue = countdown - 1; + return 0; + } + if (countdown < 20) + return 0; + + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].x, DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, owner, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + return 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/determineAIPlayerAttackRequestResponse.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp new file mode 100644 index 000000000..6f54dca38 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D38C0 + BOOLEnum AICState::determineAIPlayerAttackRequestResponse(int playerID, undefined4 param_2, int requestedByPlayerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillAttackBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp new file mode 100644 index 000000000..df8442f09 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp @@ -0,0 +1,36 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3850 + BOOLEnum AICState::determineAIPlayerHelp(int playerID, int requestedByPlayerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillHelpBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + 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/findAttackTribePositionAndPathToDefenses.cpp b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp new file mode 100644 index 000000000..b854d8312 --- /dev/null +++ b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.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 0x004CE930 + void AICState::findAttackTribePositionAndPathToDefenses(int playerID) + { + int x = 0; + int y = 0; + for (int i = 0; i < 11; i++) { + int targetUnitID = 0; + for (int j = 0; j < DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[j + DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType]) + continue; + + targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + x = DAT_UnitsState::instance.units[targetUnitID].x; + y = DAT_UnitsState::instance.units[targetUnitID].y; + break; + } + if (targetUnitID != 0) + break; + } + + DAT_GameState::instance.playerDataArray[playerID].someTile = 0; + if (x == 0) + return; + + int tile = MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::tracePathToFriendlyDefensesAndReturnTileUnk, + DAT_PathFindingState::ptr)(playerID, x, y); + if (tile <= 0) + return; + + short tileY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + DAT_GameState::instance.playerDataArray[playerID].someTile = tile; + DAT_GameState::instance.playerDataArray[playerID].someY = tileY; + DAT_GameState::instance.playerDataArray[playerID].someX + = tile - DAT_ViewportRenderState::instance.translationMatrix[tileY].addXgetTile; + } +} +} diff --git a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp new file mode 100644 index 000000000..95a84c59f --- /dev/null +++ b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp @@ -0,0 +1,52 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC420 + void AICState::generateSiegeCreationInformation(int playerID, int buildingID, int unitID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + if (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SOLITARY) + return; + + int siegeIndex; + if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TREBUCHET) + siegeIndex = 2; + else if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + siegeIndex = 3; + else + return; + + int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + int i; + for (i = 0; i < locationCount; i++) { + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i] > 0 + && DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID + == buildingID) + break; + } + if (i >= locationCount) + return; + + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID = unitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[unitID].uid; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].buildingID = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp index e77ab3b46..de9383a30 100644 --- a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp +++ b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp @@ -1,4 +1,4 @@ -#include "../AICState.hpp"" +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/AI/AITypeA.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -8,14 +8,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB120 int AICState::getAIBuildInterval(int playerID) - { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; - if (aiType == AITA_NULL) { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) return 0; - } - - return this->aics[(aiType - 1)].buildInterval; + return this->aics[aiType - 1].buildInterval; } } } diff --git a/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp new file mode 100644 index 000000000..c4ad3be31 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D12A0 + int AICState::getCurrentDesiredAttackRaidUnitCount(int aiType, int playerID) + { + if (DAT_GameCore::instance.gameMode_2 == Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == Game::TT_EXTREME) + return (DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase) + * 4 / 3; + return DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase; + } +} +} 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/getResourceRebuildDelay.cpp b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp new file mode 100644 index 000000000..5ab4f3163 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB150 + int AICState::getResourceRebuildDelay(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return this->aics[aiType - 1].resourceRebuildDelay; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp new file mode 100644 index 000000000..cea616412 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCAF0 + int AICState::getSmallestPatrolTribe(int playerID, int tribeCount) + { + int selectedTribeID = 0; + int selectedIndex = 0; + int lowestSize = 1000; + + for (int i = 0; i < tribeCount; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) { + selectedTribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedIndex = i; + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].size < lowestSize) { + lowestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedIndex = i; + } + } + + if (selectedTribeID == 0) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedIndex + 170] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedIndex + 170] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp new file mode 100644 index 000000000..b997e2dee --- /dev/null +++ b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDBC0 + int AICState::getTargetableBuildingForPlayerID(int playerID, int param_2) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return 0; + int outerPatrolGroupsCount = this->aics[aiType - 1].OuterPatrolGroupsCount; + if (outerPatrolGroupsCount <= 0) + return 0; + int tracker = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + if (tracker <= 0) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].someCounter2++; + if (DAT_GameState::instance.playerDataArray[playerID].someCounter2 >= tracker) + DAT_GameState::instance.playerDataArray[playerID].someCounter2 = 0; + + int index = (tracker / outerPatrolGroupsCount * param_2 + + DAT_GameState::instance.playerDataArray[playerID].someCounter2) + % tracker; + for (int i = 0; i < tracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + index++; + if (index >= tracker) + index = 0; + if (buildingID == 0) + continue; + + for (int j = 0; j < 8; j++) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + return buildingID; + } + } + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp new file mode 100644 index 000000000..3a4accdb0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp @@ -0,0 +1,57 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC250 + UnitType AICState::getUnitTypeForUnitBehaviourType(int playerID, AIUnitBehaviourType unitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return (UnitType)0; + int aicIndex = aiType - 1; + + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGINEERING) + return OpenSHC::Map::Units::UT_E_ENGINEER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_DIGGING) + return (UnitType)this->aics[aicIndex].AttDiggingUnit; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ASSASSINATION) + return OpenSHC::Map::Units::UT_A_ASSASSIN; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ATTUNIT2) + return (UnitType)this->aics[aicIndex].AttUnit2; + if (unitBehaviourType == OpenSHC::AI::AIUBT_LADDERING) + return OpenSHC::Map::Units::UT_E_LADDER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_TUNNELING) + return OpenSHC::Map::Units::UT_TUNNELER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_PATROLLING) + return (UnitType)this->aics[aicIndex].AttUnitPatrol; + if (unitBehaviourType == OpenSHC::AI::AIUBT_BACKUPING) + return (UnitType)this->aics[aicIndex].AttUnitBackup; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGAGING) + return (UnitType)this->aics[aicIndex].AttUnitEngage; + if (unitBehaviourType == OpenSHC::AI::AIUBT_SIEGEDEFENSE) + return (UnitType)this->aics[aicIndex].AttUnitSiegeDef; + if (unitBehaviourType != OpenSHC::AI::AIUBT_ATTUNITMAIN) + return (UnitType)this->aics[aicIndex].AttUnitMain1; + + if ((&this->aics[aicIndex] + .AttUnitMain1)[DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex >= 4) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + + int choice = DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex; + UnitType unitType = (UnitType)(&this->aics[aicIndex].AttUnitMain1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = choice + 1; + return unitType; + } + +} +} 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/giveMoveCommandToSortieUnits.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp new file mode 100644 index 000000000..30db96be3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp @@ -0,0 +1,71 @@ +#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_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD8F0 + void AICState::giveMoveCommandToSortieUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + int aicIndex = aiType - 1; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[167]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 - 1) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::syncBehaviourType7UnitTribes, this)(playerID); + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { + if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitMeleeMin) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + int unitID = DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitID; + if (unitID != 0 + && DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitUID + == DAT_UnitsState::instance.units[unitID].uid + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + (short) + DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + 0) + != 0) { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[unitID].x, + DAT_UnitsState::instance.units[unitID].y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} 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/hasNoTroopsOrAllAreDiggers.cpp b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp new file mode 100644 index 000000000..b009cbc8a --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFFD0 + BOOLEnum AICState::hasNoTroopsOrAllAreDiggers(int playerID) + { + int totalTroops = DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops; + int halfOfDiggingTroops = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState == 6) { + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 16) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } else { + halfOfDiggingTroops = DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops / 2; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 12) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } + + if (totalTroops <= 1) + return TRUE; + if (totalTroops <= 4 && halfOfDiggingTroops > totalTroops * 2) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp new file mode 100644 index 000000000..96e8e3c36 --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp @@ -0,0 +1,30 @@ +#include "OpenSHC/AI/AICState.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 0x004D00A0 + BOOLEnum AICState::hasNotEnoughSupplies(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].marketplace.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_MARKETPLACE].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].granary.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_GRANARY].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount <= 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].popularity <= 25 + && DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp new file mode 100644 index 000000000..95b2d1aeb --- /dev/null +++ b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2830 + void AICState::ifHasTribe0SendUnitsToKeep(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0]; + if (tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0]) + return; + + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp new file mode 100644 index 000000000..22d967f1f --- /dev/null +++ b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp @@ -0,0 +1,69 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.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/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD7B0 + void AICState::instructTribe166ToMove(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 0) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01--; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 50) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 = 50; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + BOOLEnum keepEnclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[166]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 - 5) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitsToTribe166, this)(playerID); + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && keepEnclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitRangedMin) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].someXPosition <= 0 + || DAT_GameState::instance.playerDataArray[playerID].someYPosition <= 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)( + tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_GameState::instance.playerDataArray[playerID].someXPosition, + DAT_GameState::instance.playerDataArray[playerID].someYPosition, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp new file mode 100644 index 000000000..d4224026c --- /dev/null +++ b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0520 + BOOLEnum AICState::isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade( + int playerID, ResourceType resourceType, int buffer) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return TRUE; + if (resourceType == Game::Resources::RT_GOLD) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[resourceType] + < this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].minimumGoodsRequiredAfterTrade + + buffer) + return FALSE; + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp new file mode 100644 index 000000000..243d4828c --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3960 + void AICState::makeOutpostTribesAttack() + { + for (int i = 0; i < 1000; i++) { + int tribeID = this->tribeIDArray[i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid != this->tribeUIDArray[i]) + continue; + + MACRO_CALL_MEMBER(AICState_Func::decideOnTribeAttackLocation, this)(tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp new file mode 100644 index 000000000..b9d4faf2f --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp @@ -0,0 +1,47 @@ +#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_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEA50 + void AICState::makeUnitsGoDefensiveAndBackToSomeLocation(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].someTile == 0) + 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_DEFENSIVE; + uint result = MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_GameState::instance.playerDataArray[playerID].someX, + DAT_GameState::instance.playerDataArray[playerID].someY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + if (result == 0) + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::removeAllTribeUnits, DAT_TribesState::ptr)( + tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp new file mode 100644 index 000000000..41ac2d515 --- /dev/null +++ b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp @@ -0,0 +1,19 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECAB0 + BOOLEnum AICState::mapperIsWoodCuttersHutWhileNoWoodAvailable( + undefined4 playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_WOODSMAN + && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp new file mode 100644 index 000000000..ee8e3ad57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp @@ -0,0 +1,47 @@ +#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_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE830 + void AICState::moveAttackTribesToLocations(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; + 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_DEFENSIVE; + 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/moveAttackingUnitsToNextRallyPoint.cpp b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp new file mode 100644 index 000000000..04aa9c19d --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp @@ -0,0 +1,71 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.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/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.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 0x004D2F80 + void AICState::moveAttackingUnitsToNextRallyPoint(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_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; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::allUnitsReachedTheirDestination, + DAT_TribesState::ptr)(tribeID) + == FALSE) + continue; + + DAT_TribesState::instance.tribes[tribeID].unknownAttackRelatedUpdateCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addRallyPointForTribe, this)(tribeID); + + short rallyPointCount = DAT_TribesState::instance.tribes[tribeID].rallyPointCount; + int rallyPointIndex = DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + 1; + if (rallyPointIndex >= rallyPointCount) + rallyPointIndex = 0; + + int x = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][0]; + int y = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][1]; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = rallyPointIndex; + + if (rallyPointCount <= 0) + continue; + + if (rallyPointCount <= 1) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= 5) + continue; + } else { + if (DAT_UnitsState::instance.units[targetUnitID].closestEnemyMicroDistance <= 96) + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp new file mode 100644 index 000000000..a7af36538 --- /dev/null +++ b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.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::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CED90 + BOOLEnum AICState::percentageNonMovingTribesGTEAICSpecified(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = 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[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttMovingTribesThreshold; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp new file mode 100644 index 000000000..4db7857fd --- /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 allowFletchers = 1; + int allowIronMines = 1; + int allowFarms = 1; + int allowPitchRigs = 1; + int allowWoodcutters = 1; + int allowPoleturners = 1; + int allowQuarries = 1; + int allowBlacksmiths = 1; + int allowArmourers = 1; + int allowTanners = 1; + int allowBakeries = 1; + int allowBreweries = 1; + int allowInns = 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; + allowFarms = 0; + allowPitchRigs = 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_WOODCUTTERSHUT] = !allowWoodcutters; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_PITCHRIG] = !allowPitchRigs; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_POLETURNER] = !allowPoleturners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_ARMOURER] = !allowArmourers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_TANNER] = !allowTanners; + 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; + + 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/playAnger1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp new file mode 100644 index 000000000..c2f18af59 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp @@ -0,0 +1,41 @@ +#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" +#include "OpenSHC/Globals/INT_ARRAY_00eb0af0.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0830 + void AICState::playAnger1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + DWORD now = timeGetTime(); + if (INT_ARRAY_00eb0af0::instance[playerID] != 0 && now - INT_ARRAY_00eb0af0::instance[playerID] < 600000) { + return; + } + + INT_ARRAY_00eb0af0::instance[playerID] = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 5); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..df0b308ad --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.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 0x004D0C00 + void AICState::playAnger2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 = 96; + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 6); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp new file mode 100644 index 000000000..64249dc9c --- /dev/null +++ b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#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_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0FC0 + void AICState::playFriendlyVictoryBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 10); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp new file mode 100644 index 000000000..ca55ee528 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#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_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B70 + void AICState::playGoodsNotSentBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 28); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..acb064856 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp @@ -0,0 +1,32 @@ +#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 0x004D0A20 + void AICState::playGoodsRequestBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 14); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..7b807ddfc --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#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 0x004D0BB0 + void AICState::playGoodsSentBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 29); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp new file mode 100644 index 000000000..639186e16 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp @@ -0,0 +1,22 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0F90 + void AICState::playJokeBikFromAIToHuman(int playerID, int aiType) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType, 20); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp new file mode 100644 index 000000000..0bdd24ea6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp @@ -0,0 +1,27 @@ +#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_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0CF0 + void AICState::playNervous1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 = 48; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 8); + } +} +} 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/playNoHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp new file mode 100644 index 000000000..07b38053e --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#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_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1050 + void AICState::playNoHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 31); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp new file mode 100644 index 000000000..a652216ca --- /dev/null +++ b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp @@ -0,0 +1,73 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.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 0x004D08B0 + void AICState::playPlayerKilledBiksFromKilledAndKillerPlayer(int killerPlayerID, int killedPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(killerPlayerID) + == 0) { + return; + } + + if (killedPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 12); + return; + } + + if (killerPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.playerDataArray[i].aiType == AITA_NULL) { + continue; + } + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + i, DAT_GameState::instance.playerDataArray[i].aiType - 1, 17); + } + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID] + == DAT_GameState::instance.mapAndTime.playerTeams[killedPlayerID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 16); + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[killerPlayerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 18); + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 13); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp new file mode 100644 index 000000000..5f1cee729 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp @@ -0,0 +1,39 @@ +#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 0x004D0D40 + void AICState::playRequestHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 = 48; + + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 19); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp new file mode 100644 index 000000000..f49839574 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp @@ -0,0 +1,41 @@ +#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" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D07C0 + void AICState::playTauntToAttackedPlayerBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int bik = 0; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + bik = 23; + } + + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + if (bik == 0) { + bik = (SEC_RNG::instance.currentNumber2 & 3) + 1; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, bik); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp new file mode 100644 index 000000000..429518496 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp @@ -0,0 +1,29 @@ +#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_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1000 + void AICState::playTeamWinningBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 30); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..823e435e9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#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" +#include "OpenSHC/Globals/DWORD_00eb0b18.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0A80 + void AICState::playThanksBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay = this->aics[aicIndex].aiRequestDelay; + + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + DWORD now = timeGetTime(); + if (DWORD_00eb0b18::instance != 0 && now - DWORD_00eb0b18::instance < 1200000) { + return; + } + + DWORD_00eb0b18::instance = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 15); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..0d11e93d4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.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 0x004D0C70 + void AICState::playVictory2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[targetPlayerID].aiType == AITA_NULL) { + return; + } + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(targetPlayerID) + == 0) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 = 48; + if (playerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + targetPlayerID, DAT_GameState::instance.playerDataArray[targetPlayerID].aiType - 1, 11); + } +} +} diff --git a/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp new file mode 100644 index 000000000..302c38a63 --- /dev/null +++ b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp @@ -0,0 +1,68 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeIndex.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::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEEA0 + BOOLEnum AICState::processAttForceRallyPercentage(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_07_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_10_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_05_01) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return (BOOLEnum)(notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttForceRallyPercentage); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp new file mode 100644 index 000000000..4b0108c3d --- /dev/null +++ b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp @@ -0,0 +1,138 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIUnitBehaviourType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC070 + AIUnitBehaviourType AICState::randomlySelectAttackUnitTypeToRecruit(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int aicIndex = aiType - 1; + int requiredCount = 0; + + // clear out flags + for (int i = 0; i < 11; i++) { + DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required = 0; + } + + int engineersCount = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (engineersCount > (int)this->aics[aicIndex].AttMaxEngineers) { + engineersCount = this->aics[aicIndex].AttMaxEngineers; + } + if (this->aics[aicIndex].AttMaxEngineers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops < engineersCount) { + requiredCount = 1; + DAT_SkirmishDefinedData::instance.attackUnitRequired[0].required = 1; + } + + if (this->aics[aicIndex].AttDiggingUnitMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops + < (int)this->aics[aicIndex].AttDiggingUnitMax + && DAT_GameState::instance.playerDataArray[attackedPlayerID].moatsOwned > 5) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[1].required = 1; + } + + if (this->aics[aicIndex].AttMaxAssassins != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + < (int)this->aics[aicIndex].AttMaxAssassins) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[2].required = 1; + } + + if (this->aics[aicIndex].AttUnit2Max != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + < (int)this->aics[aicIndex].AttUnit2Max) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[3].required = 1; + } + + if (this->aics[aicIndex].AttMaxLaddermen != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalLaddermenTroops + < (int)this->aics[aicIndex].AttMaxLaddermen) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[4].required = 1; + } + + if (this->aics[aicIndex].AttMaxTunnelers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalTunnelerTroops + < (int)this->aics[aicIndex].AttMaxTunnelers) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[5].required = 1; + } + + if (this->aics[aicIndex].AttUnitPatrolMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops + < (int)this->aics[aicIndex].AttUnitPatrolMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[6].required = 1; + } + + if (this->aics[aicIndex].AttUnitBackupMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops + < (int)this->aics[aicIndex].AttUnitBackupMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[7].required = 1; + } + + if (this->aics[aicIndex].AttUnitEngageMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + < (int)this->aics[aicIndex].AttUnitEngageMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[8].required = 1; + } + + if (this->aics[aicIndex].AttUnitSiegeDefMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + < (int)this->aics[aicIndex].AttUnitSiegeDefMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[9].required = 1; + } + + if (this->aics[aicIndex].AttMaxDefault != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops + < (int)this->aics[aicIndex].AttMaxDefault) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[10].required = 1; + } + + if (requiredCount <= 0) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int selection = (int)SEC_RNG::instance.currentNumber2 % requiredCount; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + for (int i = 0; i < 11; i++) { + if (DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required == 0) { + continue; + } + if (selection == 0) { + return (AIUnitBehaviourType)DAT_SkirmishDefinedData::instance.attackUnitRequired[i].unitBehaviourType; + } + selection--; + } + + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp new file mode 100644 index 000000000..5fae78c3f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE040 + void AICState::recomputeAIPathCostToAttackedKeep(int playerID, int useBorderDistance) + { + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int keepArea = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[playerID].campground.tileEntry]; + + int borderDistance; + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 120) { + borderDistance = 98; + } else { + borderDistance + = DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 90 ? 70 + : 50; + } + if (useBorderDistance == 0) { + borderDistance = 0; + } + + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = attackedPlayerID; + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 1, 1); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 0, 1); + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(110, borderDistance, keepArea, playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp new file mode 100644 index 000000000..2478afb94 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp @@ -0,0 +1,31 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE1A0 + void AICState::recomputeAIZonerLayer() + { + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, DAT_PathFindingState::ptr)(1, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, 1); + DAT_GameState::instance.playerDataArray[1].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp new file mode 100644 index 000000000..905549bec --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/AttackInfoSubElement.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::AttackInfoSubElement; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE110 + void AICState::recomputeAttackAIZone() + { + for (int playerID = 1; playerID < 9; playerID++) { + int fullID = DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = playerID; + if (fullID == -1 + && DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0 + // fixme: this comparison looks dumb since fullID is already known to be the value, so a cmp eax eax + // is generated, but we need it for the 100% match. + && fullID == DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(playerID, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = 1; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp new file mode 100644 index 000000000..ea0aff1d7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4630 + void AICState::recreateEngineersTribe(int playerID) + { + if (0 >= DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops) { + return; + } + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_tribeID != 0) { + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) { + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12] = 0; + } + } + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp new file mode 100644 index 000000000..d72a3a55f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -0,0 +1,162 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/Unit.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Unit; + using OpenSHC::Map::Units::UnitInstructionType; + using OpenSHC::Map::Units::UnitLogicState; + using OpenSHC::Map::Units::UnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD2A0 + void AICState::recruitHarrassingSiegeEngines(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int engineersGuild = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + + if (aiType == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 500) { + return; + } + + int aicIndex = aiType - 1; + int harrassingSiegeEnginesMax = this->aics[aicIndex].HarassingSiegeEnginesMax; + if (harrassingSiegeEnginesMax <= 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout != 0) { + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout -= 1; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harassingSiegeEnginesCountUnk + >= harrassingSiegeEnginesMax) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].unknownHarrassingSiegeRelated >= 20) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + || DAT_TribesState::instance.tribes[tribeID].size < 2) { + // Not enough engineers yet: recruit one at the engineers guild + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0 + || DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 30 + || engineersGuild <= 0 + || DAT_BuildingsState::instance.buildings[engineersGuild].buildingType + != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) { + return; + } + + uint unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + OpenSHC::Map::Units::UT_E_ENGINEER, engineersGuild, playerID, 0); + if (unitID == 0) { + return; + } + + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165]) { + tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + = DAT_TribesState::instance.tribes[tribeID].uid; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, tribeID); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = OpenSHC::AI::AIUBT_HARASS_SIEGE; + } else { + // Enough engineers: pick the next siege engine from the AIC list and place its tent + if (DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex >= 8) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + if ((&this->aics[aicIndex].HarassingSiegeEngine1)[DAT_GameState::instance.playerDataArray[playerID] + .harrassingSiegeEnginesIndex] + == 0) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + + int engineIndex = DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex; + MappersEnum siegeEngineToBuild = (MappersEnum)(&this->aics[aicIndex].HarassingSiegeEngine1)[engineIndex]; + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = engineIndex + 1; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AIVState_Func::findSpotNearEngineersGuild, DAT_AIVState::ptr)(playerID) + == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + + // Place the siege tent + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + DAT_AIVState::instance.buildingApproriateGridXPosition * 5, + DAT_AIVState::instance.buildingAppropriateGridYPosition * 5, siegeEngineToBuild, 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail != FALSE) { + return; + } + + int buildingID = DAT_TileMapState::instance.placedBuildingID; + DAT_BuildingsState::instance.buildings[buildingID].attackWave = 0; + DAT_BuildingsState::instance.buildings[buildingID].unknownSiegeTentRelated01 = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + } + + // Point all harassing siege engines at the currently attacked player + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != OpenSHC::Map::Units::ULS_INVISIBLE + && DAT_UnitsState::instance.units[unitID].dying == 0 + && (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + && DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == OpenSHC::AI::AIUBT_SIEGE_01) { + DAT_UnitsState::instance.units[unitID].siegeTargetPlayerID + = (short)DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + } + } + + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout = 8; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp new file mode 100644 index 000000000..b81a2726e --- /dev/null +++ b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE430 + void AICState::removeOrganismsAndSetMoveDestinationPairs(int playerID) + { + // Clear a tree standing on the target tile + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + // Hardcoded exception: the Sultan keeps a smaller distance than all other AIs + int extraDistance + = DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_SULTAN ? 8 : 20; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 10000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::setMoveDestinationPairs, + DAT_PathFindingState::ptr)(playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].someMinimumDistance + extraDistance); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp new file mode 100644 index 000000000..949062c85 --- /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) + { + char text[1000]; + + // One row of 24 pixels per player, starting one row below y + y += 24; + for (int playerID = 1; 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; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + 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[aicIndex].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[aicIndex].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)( + aicIndex, 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[aicIndex].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/requestGoods.cpp b/src/OpenSHC/AI/AICState/requestGoods.cpp new file mode 100644 index 000000000..2c10772ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/requestGoods.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.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::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1F00 + void AICState::requestGoods(int playerID, ResourceType resourceType, int amount) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay != 0) { + return; + } + // Never request raw farm goods or ale + if (resourceType == OpenSHC::Game::Resources::RT_WHEAT || resourceType == OpenSHC::Game::Resources::RT_HOPS + || resourceType == OpenSHC::Game::Resources::RT_ALE) { + return; + } + // Only request food when almost out of it + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + // fixme: NOTE: the original performs the exact same food check a second time, it cannot be removed + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + + // Ask every human teammate that has no pending request from this player + for (int i = 1; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] == -1) { + continue; + } + 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; + } + if (DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] != 0) { + continue; + } + + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] = resourceType; + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray2Unk[playerID] = amount; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playGoodsRequestBikFromPlayerToPlayer, this)(playerID, i); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp new file mode 100644 index 000000000..cacb4823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -0,0 +1,143 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AIC/AITargetChoiceInt.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AITargetChoice; + using OpenSHC::AI::AIC::AITargetChoiceInt; + using OpenSHC::Game::Player::PlayerData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4680 + void AICState::selectAttackTarget(int playerID) + { + int selectedPlayerID = 0; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int fromX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; + int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + int lowestDistance = 10000; + int highestGold = 0; + int lowestPoints = 1000000; + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeNervousness, this)(playerID); + + // Drop a pending request if the asking player's lord is dead + if ((DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + || DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + == 0) { + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = 0; + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) { + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk) + != 0) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; + return; + } + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + + int targetChoice = this->aics[aicIndex].TargetChoice; + if (targetChoice == OpenSHC::AI::AIC::AITC_PLAYER) { + // Closest human player + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[i].currentResources[0xf] >= highestGold) { + highestGold = DAT_GameState::instance.playerDataArray[i].currentResources[0xf]; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[i].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[i].totalTroopValue + + DAT_GameState::instance.playerDataArray[i].currentResources[0xf] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= lowestPoints) { + lowestPoints = points; + selectedPlayerID = i; + } + } + } + + // Fall back to the closest enemy + if (selectedPlayerID == 0) { + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + + if (selectedPlayerID == 0) { + return; + } + } + + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk = selectedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = selectedPlayerID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp new file mode 100644 index 000000000..11008c453 --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDAB0 + int AICState::selectBuildingFromAListOfBuildingTypes(int playerID) + { + // Pick the targetable building whose type comes first in BuildingTargetPrioritySet1 + int selectedBuildingID = 0; + int bestPriority = 46; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) { + continue; + } + for (int j = 0; j < bestPriority; j++) { + if ((short)DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) { + // Found a better match; lowering the bound also ends this loop + bestPriority = j; + selectedBuildingID = buildingID; + } + } + } + + return selectedBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp new file mode 100644 index 000000000..c5845f0bb --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCA0 + int AICState::selectRandomBuildingIDFromListOfBuildingTypes(int playerID, undefined4 param_2) + { + int count = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + if (count <= 0) { + return 0; + } + + int index = SEC_RNG::instance.currentNumber2 % count; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + return DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp new file mode 100644 index 000000000..222bd7854 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp @@ -0,0 +1,105 @@ +#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)); + int nervousActions = DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker; + for (int i = 0; i < 20; i++) { + ResourceType resourceType + = (ResourceType)DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].game; + int category = DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic; + int variance = this->aics[aicIndex].maxResourceVariance; + int resourceOffset = (playerResourceBase + resourceType) * sizeof(int); + + int amount; + if (category == OpenSHC::AI::Trading::AIRTC_WOOD) { + amount = this->aics[aicIndex].maxWood; + } else if (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = this->aics[aicIndex].maxFood; + } else if (category == OpenSHC::AI::Trading::AIRTC_STONE) { + amount = this->aics[aicIndex].maxStone; + } else if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = this->aics[aicIndex].maxEquipment; + } else if (category == OpenSHC::AI::Trading::AIRTC_ALE) { + amount = this->aics[aicIndex].maxBeer; + } else { + amount = this->aics[aicIndex].maxResourceOther; + } + amount += variance; + + if (nervousActions > 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 (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = amount / 4; + } else if (category == 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 (category != OpenSHC::AI::Trading::AIRTC_FOOD) { + if (category == 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/sellGoods.cpp b/src/OpenSHC/AI/AICState/sellGoods.cpp index 5ebd21d63..c830f93ea 100644 --- a/src/OpenSHC/AI/AICState/sellGoods.cpp +++ b/src/OpenSHC/AI/AICState/sellGoods.cpp @@ -23,6 +23,5 @@ namespace AI { MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( playerID, 1, -amount, resourceType); } - } } diff --git a/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp new file mode 100644 index 000000000..8dd7eaed8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF4F0 + void AICState::sendAITribe_0xf_toAttackPatrol(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + // Bug: only tribe 15 is sent, although tribe 16 can also exist if 'AIC: AttUnitPatrolGroupsCount' is 2 + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[15]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[15]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::sendAttackingPatrolTribeToComputedDestination, + DAT_TroopValueState::ptr)(tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp new file mode 100644 index 000000000..abe6c64a1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp @@ -0,0 +1,64 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.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::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4220 + void AICState::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed(int playerID) + { + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 6; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + + j]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + j]) { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType, + j); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + + // NOTE: the original sets the stance even when the tribe is invalid (tribeID may be 0) + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp new file mode 100644 index 000000000..5fc7b54b3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp @@ -0,0 +1,62 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.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 { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D44E0 + void AICState::sendOilMenForOilPickupOrToAIVLocations(int playerID) + { + int oilSmelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1]; + if (count < 1) { + return; + } + if (count == 10) { + count = 9; + } + + // Oil men tribes start at offset 71 in the tribe ID array + for (int i = 1; i <= count; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[70 + i]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[70 + i]) { + continue; + } + + if (DAT_UnitsState::instance.units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .resourceToDeposit + != 0) { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)( + tribeID, OpenSHC::AI::AIVUT_ENGINEER, i - 1); + continue; + } + + // Send for oil duty + if (oilSmelterID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, oilSmelterID, + DAT_BuildingsState::instance.buildings[oilSmelterID].uid, 0); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp new file mode 100644 index 000000000..0110e7fed --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" + +#include "OpenSHC/Globals/DAT_GameState.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" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4130 + void AICState::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous(int playerID) + { + for (int i = 0; i < 5; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]) { + continue; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if ((DAT_TileMapState::instance.LogicLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile] + & 0x10000100U) + == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType, j); + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp new file mode 100644 index 000000000..272c3b113 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp @@ -0,0 +1,21 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2870 + void AICState::sendTribeAtOffset1ToCampfire(int playerID) + { + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[1]; + if (_tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[1]) + return; + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(_tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp new file mode 100644 index 000000000..a93f91fa0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2E00 + void AICState::sendTribeToAIVLocationSlot(int tribeID, AIVUnitType aivUnitType, int slotIndex) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + + // Find the slotIndex-th non-empty slot + int tile = 0; + for (int i = 0; i < 10; i++) { + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i] == 0) { + continue; + } + slotIndex--; + if (slotIndex < 0) { + tile = DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i]; + break; + } + } + + 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::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + return; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, owner); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp new file mode 100644 index 000000000..82f959aed --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp @@ -0,0 +1,37 @@ +#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 0x004CF750 + void AICState::sendTribeToAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xbd]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xbd]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + 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_0x3fb); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp new file mode 100644 index 000000000..6346c66e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp @@ -0,0 +1,109 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.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/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.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 { + + using OpenSHC::AI::AIType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D30E0 + undefined4 AICState::sendUnitsToAttackBreachedCastle(int attackingPlayerIndex) + { + int vector = 0; + if (DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiType == OpenSHC::AI::AIT_NULL) { + return 1; + } + + BOOLEnum sentUnitsToAttackLord = FALSE; + int defendingLordID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, + DAT_UnitsState::ptr)(DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID); + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + if (tribeType == 18 || tribeType == 14 || tribeType == 13) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeIDs[tribeType + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeUIDs[j + tribeType]) { + continue; + } + + int targetUnitID = (short)DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + + if (sentUnitsToAttackLord != FALSE || defendingLordID == 0 || tribeType == 186) { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveSomeRaidCommand, this)(tribeID, tribeType) + != 0) { + continue; + } + + if (tribeType == 186) { + vector += 4; + } + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_8; + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + int attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y); + + if (DAT_DirectionAlgorithmState::instance.distanceHigh > 4) { + attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + vector += 1; + } else { + // Send units to attack lord + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[defendingLordID].x, + DAT_UnitsState::instance.units[defendingLordID].y, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0) + == 0) { + return 0; + } + sentUnitsToAttackLord = TRUE; + } + } + } + + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp new file mode 100644 index 000000000..c824f5d75 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp @@ -0,0 +1,24 @@ +#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" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD110 + void AICState::sendUnitsToCampfire(int tribeID, int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].campground.id <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + DAT_GameState::instance.playerDataArray[playerID].campground.yEntry, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp new file mode 100644 index 000000000..fcb585017 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD0C0 + void AICState::sendUnitsToKeep(int tribeID, int playerID) + { + int keepID = DAT_GameState::instance.playerDataArray[playerID].keep.id; + if (keepID <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + (short)DAT_BuildingsState::instance.buildings[keepID].x + 3, + (short)DAT_BuildingsState::instance.buildings[keepID].y + 4, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp new file mode 100644 index 000000000..be3532ffb --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp @@ -0,0 +1,79 @@ +#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/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_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE5F0 + void AICState::sendUnitsToPlayerIDsKeepIfPossible(int playerID1, int playerID2) + { + uint campfireX = DAT_GameState::instance.playerDataArray[playerID1].campground.xEntry; + uint campfireY = DAT_GameState::instance.playerDataArray[playerID1].campground.yEntry; + + uint gatehouseX; + uint gatehouseY; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID1, playerID2) + == FALSE) { + uint gatehouseTile = MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findOwnedGatehouse, + DAT_PathFindingState::ptr)(playerID2, campfireX, campfireY); + if (gatehouseTile != 0) { + gatehouseY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[gatehouseTile]; + gatehouseX + = gatehouseTile - DAT_ViewportRenderState::instance.translationMatrix[gatehouseY].addXgetTile; + } else { + DAT_GameState::instance.playerDataArray[playerID1].requestStateUnk = 0; + return; + } + } + + for (int i = 0; i < 11; i++) { + int tribeTypeStart = 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[playerID1].aiTribeIDs[tribeTypeStart + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID1].aiTribeUIDs[j + tribeTypeStart]) { + continue; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID1, playerID2) + != FALSE) { + if (tribeTypeStart == 0xbe) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID2); + } 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, playerID2); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID2); + } + } else { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, gatehouseX, gatehouseY, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp new file mode 100644 index 000000000..0296967f4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4340 + void AICState::sendWallPatrolUnitTribesToAIVLocations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 3; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType]; + int groups = count; + if (count > this->aics[aicIndex].defWallPatrolGroups) { + groups = this->aics[aicIndex].defWallPatrolGroups; + } + if (groups <= 0) { + continue; + } + + int stepSize = count / groups; + if (stepSize <= 0) { + stepSize = 1; + } + + for (int j = 0, patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]) { + continue; + } + + int slot + = (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + patrolGroupIndex) + % count; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType, + slot); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_01.cpp b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp new file mode 100644 index 000000000..dc4631bf4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp @@ -0,0 +1,150 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D60 + void AICState::setAICParameters_01(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].aiRequestDelay = 0x12; + this->aics[aicIndex].populationPerQuarry = 0x12; + this->aics[aicIndex].populationPerFarm = 6; + this->aics[aicIndex].maxWoodcutters = 6; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5500; + this->aics[aicIndex].highestPopularity = 7500; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 0x14; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 8; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 10; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 0x14; + this->aics[aicIndex].defWallPatrolGroups = 1; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x18; + this->aics[aicIndex].DefUnit2 = 0x18; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnit1 = 0x18; + this->aics[aicIndex].RaidUnit2 = 0x18; + this->aics[aicIndex].RaidUnit3 = 0x16; + this->aics[aicIndex].recruitProbDefDefault = 0; + this->aics[aicIndex].recruitProbDefStrong = 0; + this->aics[aicIndex].recruitProbRaidWeak = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x32; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 100; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].RaidUnitsRandom = 0xf; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0xf; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x16; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x10; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x18; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_PLAYER; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_02.cpp b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp new file mode 100644 index 000000000..281539c43 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7120 + void AICState::setAICParameters_02(int aicIndex) + { + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 0xffffffff; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xf; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0xc; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].populationPerWoodcutter = 5; + this->aics[aicIndex].populationPerQuarry = 0xf; + this->aics[aicIndex].resourceRebuildDelay = 0xc; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].maxFood = 0x96; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 6; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x30; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 500; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 400; + this->aics[aicIndex].unknown073 = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnitMax = 0x14; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 0; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitMeleeMin = 8; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 3; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].RaidUnit2 = 0x47; + this->aics[aicIndex].RaidUnit3 = 0x47; + this->aics[aicIndex].RaidUnit4 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x28; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 2; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 10; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x48; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x16; + this->aics[aicIndex].AttUnitSiegeDefMax = 0x1e; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_BALANCED; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_03.cpp b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp new file mode 100644 index 000000000..83053911a --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7520 + void AICState::setAICParameters_03(int aicIndex) + { + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 7000; + this->aics[aicIndex].highestPopularity = 0x2134; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 400; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 100; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x3c; + this->aics[aicIndex].maxWood = 0x46; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 0x19; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].OuterPatrolRallyDelay = 0x14; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x46; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x3c; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0xf; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 0x1e; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x1a; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 5; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_04.cpp b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp new file mode 100644 index 000000000..f2043fc77 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp @@ -0,0 +1,149 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7910 + void AICState::setAICParameters_04(int aicIndex) + { + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0x14; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 4; + this->aics[aicIndex].populationPerQuarry = 9; + this->aics[aicIndex].populationPerIronmine = 0xd; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 6; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 2; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 4; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0xfa; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidStrong = 10; + this->aics[aicIndex].recruitProbAttackDefault = 10; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].OuterPatrolRallyDelay = 3; + this->aics[aicIndex].RaidUnitsRandom = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].SortieUnitRangedMin = 8; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnitMax = 8; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 100; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].RaidUnit2 = 0x4a; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0xf; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0xc; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 8; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0xc; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 5; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_CLOSEST; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_05.cpp b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp new file mode 100644 index 000000000..52b7ad888 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7D20 + void AICState::setAICParameters_05(int aicIndex) + { + this->aics[aicIndex].maxFood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x14; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x14; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 2; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxWoodcutters = 0xe; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x14; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x22; + this->aics[aicIndex].recruitProbDefStrong = 0x19; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitMeleeMin = 7; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceSupportAllyThreshold = 8; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0x19; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].DefUnit1 = 0x4b; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].DefUnit5 = 0x4c; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].recruitProbRaidDefault = 0x21; + this->aics[aicIndex].recruitProbAttackDefault = 0x21; + this->aics[aicIndex].DefTotal = 0x46; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 1; + this->aics[aicIndex].AttCoordinationPreference = 0x3c; + this->aics[aicIndex].CowThrowInterval = 2000; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 5; + this->aics[aicIndex].AttUnit2 = 0x4a; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0x4c; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_06.cpp b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp new file mode 100644 index 000000000..cd71250b5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8110 + void AICState::setAICParameters_06(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x32; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerPitchrig = 7; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 10; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 5; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x78; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].SortieUnitRangedMin = 7; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x1e; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x3c; + this->aics[aicIndex].DefTotal = 0x3c; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x4c; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 0x1e; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0xf; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x46; + this->aics[aicIndex].AttForceRallyPercentage = 100; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 2; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 10; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 7; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x4b; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x46; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 5; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_07.cpp b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp new file mode 100644 index 000000000..eb1dbdaa2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8530 + void AICState::setAICParameters_07(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 1; + this->aics[aicIndex].taxesMax = 8; + this->aics[aicIndex].farm1 = 0; + this->aics[aicIndex].populationPerFarm = 100; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 500; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x1e; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x14; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 2; + this->aics[aicIndex].populationPerQuarry = 5; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 0xf; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].buildInterval = 5; + this->aics[aicIndex].resourceRebuildDelay = 0x50; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].DefDiggingUnitMax = 2; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].RecruitInterval = 8; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 0; + this->aics[aicIndex].SortieUnitRanged = 0x48; + this->aics[aicIndex].SortieUnitMeleeMin = 3; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].DefUnit1 = 0x48; + this->aics[aicIndex].DefUnit2 = 0x48; + this->aics[aicIndex].DefUnit3 = 0x4b; + this->aics[aicIndex].RaidUnitsBase = 0x14; + this->aics[aicIndex].RaidUnit1 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 5; + this->aics[aicIndex].AttForceRallyPercentage = 0x1e; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x28; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0; + this->aics[aicIndex].AttDiggingUnitMax = 0; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x78; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_08.cpp b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp new file mode 100644 index 000000000..020f996a2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp @@ -0,0 +1,149 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8920 + void AICState::setAICParameters_08(int aicIndex) + { + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 10; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x16; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x46; + this->aics[aicIndex].recruitProbAttackStrong = 0x5a; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefUnit3 = 0x19; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 10; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 2; + this->aics[aicIndex].AttCoordinationPreference = 10; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x10; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 6; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 3; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 4; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 2; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_09.cpp b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp new file mode 100644 index 000000000..3c462c06b --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8D20 + void AICState::setAICParameters_09(int aicIndex) + { + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 0x157c; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0xc; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 5; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x1e; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = -999; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 10; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].AttForceRandom = 4; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].SortieUnitRangedMin = 0xc; + this->aics[aicIndex].SortieUnitMelee = 0x1b; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnitsRandom = 0; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 6; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x28; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x12; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x17; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x17; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_10.cpp b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp new file mode 100644 index 000000000..58aebe7c6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9120 + void AICState::setAICParameters_10(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 6; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalWeak = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefTotal = 0x14; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefWalls = 4; + this->aics[aicIndex].DefUnit1 = 0x1c; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].DefUnit5 = 0x16; + this->aics[aicIndex].DefUnit6 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x28; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x3c; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0x18; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttUnitMain2 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_11.cpp b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp new file mode 100644 index 000000000..36c716dd8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp @@ -0,0 +1,145 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C94F0 + void AICState::setAICParameters_11(int aicIndex) + { + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].minimumApples = -1; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 6; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x1e; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].SortieUnitRanged = 0x4a; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].DefUnit2 = 0x4c; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x5a; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 9; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 10; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_12.cpp b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp new file mode 100644 index 000000000..c08fc909d --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9900 + void AICState::setAICParameters_12(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].maxFood = 0x50; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x4b; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine3 = 0x166; + this->aics[aicIndex].HarassingSiegeEnginesMax = 3; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x3d; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_13.cpp b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp new file mode 100644 index 000000000..c4318fa1f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C9CF0 + void AICState::setAICParameters_13(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0x14; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitMeleeMin = 0xf; + this->aics[aicIndex].SortieUnitMelee = 0x46; + this->aics[aicIndex].DefDiggingUnitMax = 0x3c; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x49; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0x28; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x49; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_14.cpp b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp new file mode 100644 index 000000000..bee77c542 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA0C0 + void AICState::setAICParameters_14(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 6; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 8; + this->aics[aicIndex].maxFood = 300; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x1e; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0x32; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x14; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x1a; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].DefUnit4 = 0x4c; + this->aics[aicIndex].DefUnit5 = 0x49; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0x166; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x32; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 8; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].SiegeEngine5 = 0x4d; + this->aics[aicIndex].CowThrowInterval = 6; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x50; + this->aics[aicIndex].AttUnit2 = 0x1a; + this->aics[aicIndex].AttUnit2Max = 10; + this->aics[aicIndex].AttMaxAssassins = 10; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4c; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttUnitMain2 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_15.cpp b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp new file mode 100644 index 000000000..2d2c9deca --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004CA4D0 + void AICState::setAICParameters_15(int aicIndex) + { + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].farm4 = 0x21; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 7; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxResourceOther = 0xc; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 6; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 9; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].recruitProbRaidWeak = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].SortieUnitMelee = 0x1c; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].RaidUnitsBase = 0x10; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_16.cpp b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp new file mode 100644 index 000000000..1721cd187 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA8A0 + void AICState::setAICParameters_16(int aicIndex) + { + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 0x32; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 0; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x25; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x25; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 8; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x25; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp new file mode 100644 index 000000000..b6a98e489 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp @@ -0,0 +1,33 @@ +#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 0x004D0110 + void AICState::setAttackCoordinationLevel(int playerID) + { + int const _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == AITA_NULL) { + return; + } + int _willingnessPercentage = this->aics[_aiType - 1].AttCoordinationPreference + + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (90 < _willingnessPercentage) { + _willingnessPercentage = 90; + } + int _rngMod100 = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + if (_rngMod100 < _willingnessPercentage / 2) { + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel = 2; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel + = _rngMod100 < _willingnessPercentage; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp new file mode 100644 index 000000000..db0febac5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDFD0 + void AICState::setAttackPrimeInfo(int playerID, int attackedPlayerID) + { + int tile = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile; + DAT_TroopValueState::instance.attackInfo.startZone = (char)DAT_TileMapState::instance.AIZoneLayer[tile]; + DAT_TroopValueState::instance.attackInfo.startCon = (short)DAT_TileMapState::instance.PathConnectionLayer[tile]; + DAT_TroopValueState::instance.attackInfo.zoneSize + = DAT_PathFindingState::instance.zoneSizesArray[DAT_TroopValueState::instance.attackInfo.startCon]; + DAT_TroopValueState::instance.attackInfo.unknownOne_0x20f90 = 1; + DAT_TroopValueState::instance.attackInfo.keepCon + = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[attackedPlayerID].campground.tileEntry]; + } +} +} 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/setCurrentAttackStrength.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp new file mode 100644 index 000000000..90217ac8e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp @@ -0,0 +1,38 @@ +#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 0x004CDE60 + void AICState::setCurrentAttackStrength(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)(); + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + = (this->aics[aicIndex].AttForceRandom * randomPercent) / 100; + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave++; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] > 10000) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 7; + } else { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 5; + } + + if (DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength > 200) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 200; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp new file mode 100644 index 000000000..bf1b2a003 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp @@ -0,0 +1,38 @@ +#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 0x004CF5E0 + void AICState::setEightTribe0xc0TribesToAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + for (int i = 0; i < 8; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[i + 0xc0]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 0xc0]) { + 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/setFoodBuyPlan.cpp b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp index 981ad02d9..780b9a3d4 100644 --- a/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp +++ b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp @@ -15,7 +15,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB060 void AICState::setFoodBuyPlan(int playerID) { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; // Early return if not an AI player (human player has aiType == 0) diff --git a/src/OpenSHC/AI/AICState/setInitialAIC.cpp b/src/OpenSHC/AI/AICState/setInitialAIC.cpp new file mode 100644 index 000000000..0f1970d6d --- /dev/null +++ b/src/OpenSHC/AI/AICState/setInitialAIC.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D18B0 + void AICState ::setInitialAIC() + { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::wipeAICMemory, this)(); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_01, this)(1); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_02, this)(2); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_03, this)(3); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_04, this)(4); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_05, this)(5); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_06, this)(6); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_07, this)(7); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_08, this)(8); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_09, this)(9); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_10, this)(10); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_11, this)(0xb); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_12, this)(0xc); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_13, this)(0xd); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_14, this)(0xe); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_15, this)(0xf); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_16, this)(0x10); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp new file mode 100644 index 000000000..01b79f53f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB1B0 + int AICState::setNextFarmTypeToBuild(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex++; + if (DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex >= 8) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + if ((&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex] == 0) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + return (&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp new file mode 100644 index 000000000..56eda5317 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.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 0x004CE3B0 + void AICState::setNextMoveLocationForUnits(int playerID) + { + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 4000, FALSE); + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::setDestinationPairsBasedOnPreviousSearch, + DAT_PathFindingState::ptr)( + playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp new file mode 100644 index 000000000..f9d369e1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp @@ -0,0 +1,106 @@ +#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/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CACB0 + void AICState::setTargetForTribeBasedOnTargetChoiceLogic(int owner, int tribe) + { + if (DAT_GameState::instance.playerDataArray[owner].aiType == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[owner].aiType - 1; + + int targetPlayer = 0; + int minDistance = 10000; + int maxGold = 0; + int minPoints = 1000000; + int ownerKeepX = DAT_GameState::instance.playerDataArray[owner].campground.xEntry; + int ownerKeepY = DAT_GameState::instance.playerDataArray[owner].campground.yEntry; + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + + if (this->aics[aicIndex].TargetChoice == AIC::AITC_PLAYER) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] != -1 + && DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] + >= maxGold) { + maxGold + = DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD]; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[player].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[player].totalTroopValue + + DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= minPoints) { + minPoints = points; + targetPlayer = player; + } + } + } + + if (targetPlayer == 0) { + // Fallback: pick the closest enemy with a living lord + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } + + if (targetPlayer == 0) { + return; + } + } + + DAT_TribesState::instance.tribes[tribe].targetPlayer_outpost = (short)targetPlayer; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp new file mode 100644 index 000000000..bedc117df --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp @@ -0,0 +1,41 @@ +#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" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF560 + void AICState::setTribe0x11ToAggressiveAndAttackIfConditionMet(int playerID) + { + if (DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].moat2 <= 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[17]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[17]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + 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_0x3f7); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp new file mode 100644 index 000000000..c60bbedf3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp @@ -0,0 +1,37 @@ +#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 0x004CF400 + void AICState::setTribe0xCtoAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + 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/setTribe0xbToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp new file mode 100644 index 000000000..1dc2cf667 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp @@ -0,0 +1,37 @@ +#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 0x004CF6E0 + void AICState::setTribe0xbToAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + 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/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/setupWorkshopProductionType.cpp b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp new file mode 100644 index 000000000..94036fdec --- /dev/null +++ b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Game/Resources/ResourceTypeShort.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::Game::Resources::ResourceTypeShort; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBE30 + void AICState::setupWorkshopProductionType(int playerID, int buildingID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AIT_NULL) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_BLACKSMITH) { + int setting = this->aics[aicIndex].blacksmithSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_BLACKSMITH); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SWORD) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SWORD; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SWORD; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_MACE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_MACE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_POLETURNER) { + int setting = this->aics[aicIndex].poleturnerSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_POLETURNER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SPEAR) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SPEAR; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SPEAR; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_PIKE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_PIKE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_FLETCHER) { + int setting = this->aics[aicIndex].fletcherSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_FLETCHER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_BOW) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_BOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_BOW; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_CROSSBOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_CROSSBOW; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp new file mode 100644 index 000000000..da204a269 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp @@ -0,0 +1,77 @@ +// disable deprecation warnings for strcpy +#pragma warning(disable : 4996) + +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/DE/SHCDE/eTextSections.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + // FIXME: The original function never sets a return value (eax is left over from the last computation), + // so it was most likely a void function. The header declares int, which forces the "return 0"s below + // and costs an extra "xor eax, eax" in the assembly. Change the return type to void in the header. + + // FUNCTION: STRONGHOLDCRUSADER 0x004D05D0 + void AICState::shareGoldAmongTeamMembers(int playerID, int gold) + { + int candidateCount = 0; + int candidates[9]; + for (int i = 1; i < 9; i++) { + if ((DAT_GameSynchronyState::instance.currentAIArray[i] != 0 + || DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) + && i != playerID + && DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID] + && DAT_GameState::instance.playerDataArray[i].lordKilledByPlayerID == 0) { + candidates[candidateCount] = i; + candidateCount++; + } + } + + if (candidateCount == 0) { + return; + } + + int goldPerPlayer = gold / candidateCount; + if (goldPerPlayer == 0) { + return; + } + + for (int j = 0; j < candidateCount; j++) { + int targetPlayer = candidates[j]; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[targetPlayer] != -1) { + // "Goods received from ally" + strcpy(DAT_GameSynchronyState::instance.receivedChatMessage, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_ALLIES2, 1)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, 0); + + // "Gold" + MACRO_CALL(OS_Func::_sprintf)(DAT_GameSynchronyState::instance.receivedChatMessage, "%d %s - ", + goldPerPlayer, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_GOODS, Game::Resources::RT_GOLD)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= goldPerPlayer; + DAT_GameState::instance.playerDataArray[targetPlayer].currentResources[Game::Resources::RT_GOLD] + += goldPerPlayer; + } + return; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp new file mode 100644 index 000000000..ea63343e2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.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 0x004CB890 + BOOLEnum AICState::shouldDestroyWoodCutterUnk(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return FALSE; + } + + int buildingID; + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) { + buildingID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT); + } else { + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] <= 20) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 2) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].someCount10 < 25) { + return FALSE; + } + buildingID = DAT_GameState::instance.playerDataArray[playerID].someCount11; + } + + if (buildingID != 0) { + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + (short)DAT_BuildingsState::instance.buildings[buildingID].x, + (short)DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)( + buildingID); + return TRUE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp new file mode 100644 index 000000000..51ca93934 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA60 + BOOLEnum AICState::shouldNotBeBuildingArmoury(PlayerID playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_ARMOURY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].isArmouryAlmostFilledUp == 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp new file mode 100644 index 000000000..5ef58f276 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA30 + BOOLEnum AICState::shouldNotBeBuildingGranary(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_GRANARY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].granaryIsAlmostFilledUp == FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp new file mode 100644 index 000000000..d14e60a15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB5B0 + BOOLEnum AICState::shouldNotBeBuildingHovel(PlayerID playerID, Commands::MappersEnum param_2) + { + if (param_2 != Commands::M_MAPPER_HOVEL) { + return FALSE; + } + + int const populationCap = DAT_GameState::instance.playerDataArray[playerID].populationCap; + if (populationCap <= 12) { + return FALSE; + } + + if (populationCap <= DAT_GameState::instance.playerDataArray[playerID].currentPopulation) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 5) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk >= 5) { + return TRUE; + } + + return DAT_GameState::instance.playerDataArray[playerID].popularity < 5000; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp new file mode 100644 index 000000000..d44c311ef --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA90 + BOOLEnum AICState::shouldNotBuildKillingPits(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_KILLING_PIT) { + return DAT_AIVState::instance.mapExtraInfo.playerTotalKillingPits[playerID] >= 100; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp new file mode 100644 index 000000000..c1ac9a6b1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB9F0 + BOOLEnum AICState::shouldNotBuildMarketArmoryGranaryBarracksMercenarypost( + int playerID, Commands::MappersEnum param_2) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + // nervous: reject market/mercenarypost/barracks/armory types + if (param_2 != Commands::M_MAPPER_TRADEPOST && param_2 != Commands::M_MAPPER_BARRACKS_ARAB + && param_2 != Commands::M_MAPPER_BARRACKS_EURO && param_2 != Commands::M_MAPPER_ARMOURY) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } else { + // not nervous: only reject if destroy tracker > 0 and not marketplace + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker > 0 + && param_2 != Commands::M_MAPPER_TRADEPOST) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp new file mode 100644 index 000000000..83caadc1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp @@ -0,0 +1,50 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB700 + BOOLEnum AICState::shouldWaitWithBuildingAPositiveFearFactor(int playerID, Commands::MappersEnum param_2) + { + switch (param_2) { + case Commands::M_MAPPER_GARDEN1: + case Commands::M_MAPPER_GARDEN2: + case Commands::M_MAPPER_GARDEN3: + case Commands::M_MAPPER_GARDEN4: + case Commands::M_MAPPER_GARDEN5: + case Commands::M_MAPPER_GARDEN6: + case Commands::M_MAPPER_GARDEN7: + case Commands::M_MAPPER_GARDEN8: + case Commands::M_MAPPER_GARDEN9: + case Commands::M_MAPPER_GARDEN10: + case Commands::M_MAPPER_GARDEN11: + case Commands::M_MAPPER_GARDEN12: + case Commands::M_MAPPER_MAYPOLE: + case Commands::M_MAPPER_STATUE1: + case Commands::M_MAPPER_STATUE2: + case Commands::M_MAPPER_STATUE3: + case Commands::M_MAPPER_STATUE4: + case Commands::M_MAPPER_STATUE5: + case Commands::M_MAPPER_SHRINE1: + case Commands::M_MAPPER_SHRINE2: + case Commands::M_MAPPER_SHRINE3: + case Commands::M_MAPPER_SHRINE4: + case Commands::M_MAPPER_SHRINE5: + case Commands::M_MAPPER_DANCING_BEAR: + case Commands::M_MAPPER_POND1: + case Commands::M_MAPPER_POND2_SMALL: + case Commands::M_MAPPER_POND3_LARGE1: + case Commands::M_MAPPER_POND4_LARGE2: + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay += 1; + if (DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay < 200) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay = 0; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp new file mode 100644 index 000000000..768c6f8de --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB610 + BOOLEnum AICState::shouldWaitWithBuildingNegativeFearFactor(int playerID, Commands::MappersEnum mapper) + { + switch (mapper) { + case Commands::M_MAPPER_GALLOWS: + case Commands::M_MAPPER_STOCKS: + case Commands::M_MAPPER_CESS_PIT1: + case Commands::M_MAPPER_CESS_PIT2: + case Commands::M_MAPPER_CESS_PIT3: + case Commands::M_MAPPER_CESS_PIT4: + case Commands::M_MAPPER_BURNING_STAKE: + case Commands::M_MAPPER_GIBBET: + case Commands::M_MAPPER_DUNGEON: + case Commands::M_MAPPER_RACK_STRETCHING: + case Commands::M_MAPPER_RACK_FLOGGING: + case Commands::M_MAPPER_CHOPPING_BLOCK: + case Commands::M_MAPPER_DUNKING_STOOL: + if (DAT_GameState::instance.playerDataArray[playerID].popularity < 5000) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay++; + if (DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay < 100) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay = 0; + return FALSE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp new file mode 100644 index 000000000..f5c367225 --- /dev/null +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -0,0 +1,62 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC990 + int AICState::smallestTribeOfUnitType(int playerID, AIVUnitType unitType) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return 0; + } + + int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = maxGroups; + } + + int smallestTribeID = 0; + int smallestIndex = 0; + int smallestSize = 1000; + int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; + for (int i = 0; i < count; i++) { + 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]) { + // FIXME: The original binary jumps from here directly into the shared assignment code at the + // end of the function, skipping the "smallestTribeID == 0" check. A goto reproduces this and + // matches the assembly much better (~55% vs ~41%), but we avoid goto, so the assignment is + // duplicated here. + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)( + playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + smallestTribeID = tribeID; + smallestIndex = i; + } + } + + if (smallestTribeID == 0) { + return 0; + } + + 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/someChatMessageSelection.cpp b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp new file mode 100644 index 000000000..f6194c79a --- /dev/null +++ b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp @@ -0,0 +1,127 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Commands/GameCommandType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Commands::GameCommandType; + using OpenSHC::Game::GameMode; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D10B0 + void AICState::someChatMessageSelection() + { + int candidates[9]; + int candidateCount; + int total; + int i; + int playerID; + + if ((MACRO_CALL_MEMBER(OpenSHC::Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) + && (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SKIRMISH_SINGLE_PLAYER)) { + + if (this->aiTauntResponseTimeWindow == 0) { + return; + } + + if (timeGetTime() - this->aiTauntResponseTimeWindow <= 2000) { + return; + } + + candidateCount = 0; + // Players 1..8, manually unrolled by 4 in the original. + for (playerID = 1; playerID < 9; playerID += 4) { + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 1] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 1] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 1; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 2] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 2] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 2; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 3] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 3] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 3; + candidateCount++; + } + } + + if (candidateCount > 0) { + total = 0; + // NOTE: 'i = total' keeps the (redundant) loop entry check of the original. + for (i = total; i < candidateCount; i++) { + total += DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[candidates[i]].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + } + + if (total != 0) { + int roll = (int)SEC_RNG::instance.currentNumber1; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber1, SEC_RNG::ptr)(); + roll = roll % total; + + for (i = 0; i < candidateCount; i++) { + playerID = candidates[i]; + roll -= DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + if (roll < 0) { + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[1] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[2] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[3] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[4] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[5] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[6] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[7] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[8] = 1; + DAT_GameSynchronyState::instance.DAT_ChatTauntOrMessage = -2 + - DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeCount; + DAT_GameSynchronyState::instance.DAT_GameCommandParam0 = playerID; + MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::queueCommand, + DAT_GameSynchronyState::ptr)(OpenSHC::Commands::GCT_TAUNT_OR_CHAT); + break; + } + } + } + } + } + + this->aiTauntResponseTimeWindow = 0; + } + +} +} 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/teamIsWeakRelativeToEnemy.cpp b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp new file mode 100644 index 000000000..480816f06 --- /dev/null +++ b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0190 + BOOLEnum AICState::teamIsWeakRelativeToEnemy(int playerID, BOOLEnum param_2) + { + // strength of the own team in percent of the enemy strength + int _relativeStrength; + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue == 0) { + _relativeStrength = 100; + } else { + _relativeStrength = DAT_GameState::instance.playerDataArray[playerID].totalTeamTroopValue * 100 + / DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + } + DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy = _relativeStrength; + + // the team counts as weak below a threshold that is lower with an enclosed keep + // and much higher during the first 12000 ticks + if (param_2) { + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 70) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 300) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 100) { + return FALSE; + } + } else { + // without enemy ranged troops there is no reason to become nervous + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyRangedTroopValue < 100) + return FALSE; + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 50) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 250) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 75) { + return FALSE; + } + } + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp new file mode 100644 index 000000000..e71dc1e49 --- /dev/null +++ b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/Skirmish/SkirmishDefinedData.hpp" + +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Game::Skirmish::SkirmishDefinedData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC8D0 + undefined4 AICState::unitIDIsRangedOrArmored(int unitID) + { + int _unitType = DAT_UnitsState::instance.units[unitID].unitType; + + for (int i = 0; i < 7; i++) { + if (DAT_SkirmishDefinedData::instance.RangedAndArmoredUnits[i] == _unitType) { + return TRUE; + } + } + + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp new file mode 100644 index 000000000..eef4ddd57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D53E0 + void AICState::updateAIBehaviour() + { + if (DAT_GameState::instance.mapAndTime.dayTicks == 10) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeOutpostTribesAttack, this)(); + + // every AI gets its own tick of the day, week and month, so the work is spread out + for (int _playerID = 1; _playerID < 9; _playerID++) { + if (DAT_GameState::instance.playerDataArray[_playerID].aiType == OpenSHC::AI::AIT_NULL) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].playerDeathRelated != 0) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].lordKilledByPlayerID != 0) + continue; + int _aicIndex = DAT_GameState::instance.playerDataArray[_playerID].aiType - 1; + + if (DAT_GameState::instance.mapAndTime.dayTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieRangedUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieMeleeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recruitHarrassingSiegeEngines, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignMoatDiggers, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCreateSiegeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineers, this)(_playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiBuyAndSellGoods, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveRaidInstructions, this)(_playerID); + } + + if (DAT_GameState::instance.mapAndTime.weekTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineerForOilDuty, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateTribeLocations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIPlayerState, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiUpdateTaxesAndRations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setFoodBuyPlan, this)(_playerID); + + // on the month tick the strength update happens below, with the nervousness handling enabled + if (DAT_GameState::instance.mapAndTime.monthTicks != _playerID) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 0); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay != 0) + DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay--; + if (DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown != 0) + DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6--; + } + + if (DAT_GameState::instance.mapAndTime.monthTicks == _playerID) { + MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countMoatsOwnedByEachPlayer, DAT_TileMapState::ptr)(); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::instructTribe166ToMove, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToSortieUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectAttackTarget, this)(_playerID); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending = 1; + } else { + // only spend on recruiting while gold is above the AIC's threshold + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending + = DAT_GameState::instance.playerDataArray[_playerID].currentResources[0xf] + > this->aics[_aicIndex].recruitGoldThreshold; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp new file mode 100644 index 000000000..5050ed89e --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp @@ -0,0 +1,283 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Audio/SFX/SFXState.func.hpp" +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SFXState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D49E0 + void AICState::updateAIPlayerState(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + // an ally asked for help and is still alive: drop everything and go to their keep + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk != 1 + && DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToPlayerIDsKeepIfPossible, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk); + return; + } + + int _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + if (_aiPlayerState == 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( + playerID)) { + // send own units back to keep or campfire + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRetreatUnits, this)(playerID); + return; + } + + // proceed with attack + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); + // fixme: struct layout. This looks like an int[10][8] attack tracker starting at + // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). + (&DAT_GameState::instance + .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .attackedByPlayerArrayUnk)[playerID * 8]++; + } else if ((MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID) + && _aiPlayerState != 9) + || (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && _aiPlayerState <= 5) + || DAT_GameState::instance.playerDataArray[playerID].attackTicker >= 200) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 8; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + + if (!MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // attacked player is dead + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState <= 3) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)( + playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState < 7) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 7; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearOutdatedAITribes, this)(playerID); + + _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + + if (_aiPlayerState == 1) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recreateEngineersTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::initializeGatePathfindingForOwner, + DAT_BuildingsState::ptr)(playerID); + // computes the first siege location for units + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeEnemyKeepApproachTile, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile > 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackTribesToLocations, this)(playerID); + return; + } + // no approach tile found: back to idle + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + return; + } + + if (_aiPlayerState == 2) { + // gathering: wait until enough of the attack force has rallied + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiChoiceAttForceRallyPercentage, this)(playerID)) + return; + // send the attacking units to move closer + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 3; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setNextMoveLocationForUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0xe_toPlaceTunnels, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playTauntToAttackedPlayerBikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 3) { + // send the attacking units closer to the castle + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::processAttForceRallyPercentage, this)(playerID)) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 4; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + return; + } + + if (_aiPlayerState == 4) { + // siege: wait AttAssaultDelay (skipped without siege engines) before the assault + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::countSiegeEngineUnits, this)(playerID) <= 0) + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 100; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue++; + if (DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue + <= this->aics[_aicIndex].AttAssaultDelay) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Audio::SFX::SFXState_Func::playSFXAtLocationFullVolume, DAT_SFXState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, 0x14); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // castle is closed: attack the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe11StandGroundToGate, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe13DefensiveWave, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 5) { + // attack walls + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01++; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 >= 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandWallAssaultTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0x11ToAggressiveAndAttackIfConditionMet, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setEightTribe0xc0TribesToAggressiveAndAttack, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xCtoAggressiveAndAttack, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xbToAggressiveAndAttack, this)(playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + && !MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 6) { + // enemy castle breached + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + int _attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(_attackedPlayerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToAttackBreachedCastle, this)(playerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + // the breach got closed again: back to attacking the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + + if (_aiPlayerState == 7) { + // target died mid-attack: linger for AttUnitPatrolRecommandDelay, then retreat + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue++; + if (DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue + <= this->aics[_aicIndex].AttUnitPatrolRecommandDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + return; + } + + if (_aiPlayerState == 8) { + // retreat from attack + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playAnger1BikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 9) { + // retreating: back to idle once the units are home (or after 20 ticks) + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker < 20 + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::percentageNonMovingTribesGTEAICSpecified, this)( + playerID) + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp new file mode 100644 index 000000000..249bea57d --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp @@ -0,0 +1,212 @@ +#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 _aiStrengthFeeling = DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling; + int _recruitProbDefensive = (&this->aics[_aicIndex].recruitProbDefDefault)[_aiStrengthFeeling]; + int _recruitProbAttack = (&this->aics[_aicIndex].recruitProbAttackDefault)[_aiStrengthFeeling]; + int _recruitProbRaid = (&this->aics[_aicIndex].recruitProbRaidDefault)[_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/src/OpenSHC/AI/AICState/updateTribeLocations.cpp b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp new file mode 100644 index 000000000..83f75e037 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" + +#include "OpenSHC/Globals/DAT_EntityState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4940 + void AICState::updateTribeLocations(int playerID) + { + AITypeInt _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + + if (_aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + 1; + + if (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + >= this->aics[_aiType - 1].defWallPatrolRallyTime) { + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + 1; + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter = 0; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::ifHasTribe0SendUnitsToKeep, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeAtOffset1ToCampfire, this)(playerID); + + if (DAT_GameState::instance.mapAndTime.monthTicks == playerID) { + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::digNearestMoat, this)(playerID); + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous, this)(playerID); + + MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendWallPatrolUnitTribesToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendOilMenForOilPickupOrToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveOuterPatrolCommand, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp new file mode 100644 index 000000000..01599292d --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp @@ -0,0 +1,105 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEFD0 + void AICState::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers(int playerID) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = _aiType - 1; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) + return; + + int _availableEngineers = DAT_TribesState::instance.tribes[_aiTribeID].size; + int _requiredEngineers; + int _totalRequiredEngineers = 0; + + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + break; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + break; + } + } + if (_totalRequiredEngineers + _requiredEngineers > _availableEngineers) + break; + _totalRequiredEngineers += _requiredEngineers; + } + + if (_totalRequiredEngineers <= 0) + return; + + for (; DAT_TribesState::instance.tribes[_aiTribeID].size != 0; _totalRequiredEngineers--) { + if (_totalRequiredEngineers <= 0) + break; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)( + _aiTribeID); + } + + MappersEnum _siegeEngineType; + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + return; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + _siegeEngineType + = (MappersEnum)DAT_SkirmishDefinedData::instance.SiegeEngineCommandBuildingTypes[j]; + break; + } + } + if (_requiredEngineers > _availableEngineers) + return; + _availableEngineers -= _requiredEngineers; + + int _tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToSelection, this)( + playerID, _requiredEngineers); + if (_tribeID == 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; + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)(_tribeID, _siegeEngineType, 60, (UnitInstructionType)15); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp new file mode 100644 index 000000000..f9a3597ee --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp @@ -0,0 +1,67 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF180 + void AICState::useAITribe_0xe_toPlaceTunnels(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xe]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xe]) + return; + int _countdown = DAT_TribesState::instance.tribes[_aiTribeID].size; + + while (DAT_TribesState::instance.tribes[_aiTribeID].size != 0) { + if (_countdown <= 0) + break; + + int _unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)(_aiTribeID); + // Yes this line has to be right here for the code to match. + _countdown--; + + int _tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + DAT_TribesState::instance.tribes[_tribeID].tribeType = OpenSHC::AI::Tribes::AITT_TUNNELERS; + + DAT_TribesState::instance.tribes[_tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x415; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + _unitID, _tribeID); + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)( + _tribeID, OpenSHC::Commands::M_MAPPER_TUNNEL_CONSTRUCTION, 50, (UnitInstructionType)21); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/wipeAICMemory.cpp b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp new file mode 100644 index 000000000..ed4a02035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D30 + void AICState::wipeAICMemory() + { + for (int i = 0; i < 20; i++) { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 676, '\0', &this->aics[i]); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/yieldCowThrow.cpp b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp new file mode 100644 index 000000000..c381e7b3e --- /dev/null +++ b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFF60 + BOOLEnum AICState::yieldCowThrow(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType != AITA_NULL) { + int aicIndex = aiType - 1; + if (DAT_GameState::instance.mapAndTime.skirmishNoCowThrowing == 0 + && this->aics[aicIndex].CowThrowInterval != 0 + && DAT_GameState::instance.playerDataArray[playerID].counter > 0) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter + > this->aics[aicIndex].CowThrowInterval) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter = 0; + return TRUE; + } + } + } + return FALSE; + } +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b80b8635f..a51ca345a 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 | 92.2% | Constant store order differs -SHC_3BB0A8C1_0x004C7120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7120 | 83.7% | Constant store order differs -SHC_3BB0A8C1_0x004C7520 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7520 | 94.1% | Constant store order differs -SHC_3BB0A8C1_0x004C7910 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7910 | 81.9% | Constant store order differs -SHC_3BB0A8C1_0x004C7D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7D20 | 81.5% | Constant store order differs -SHC_3BB0A8C1_0x004C8110 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8110 | 84.3% | Constant store order differs -SHC_3BB0A8C1_0x004C8530 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8530 | 94.7% | Constant store order differs -SHC_3BB0A8C1_0x004C8920 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8920 | 87.5% | Constant store order differs -SHC_3BB0A8C1_0x004C8D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8D20 | 90.4% | Constant store order differs -SHC_3BB0A8C1_0x004C9120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9120 | 80.0% | Constant store order differs -SHC_3BB0A8C1_0x004C94F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C94F0 | 89.5% | Constant store order differs -SHC_3BB0A8C1_0x004C9900 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9900 | 84.2% | Constant store order differs -SHC_3BB0A8C1_0x004C9CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9CF0 | 83.3% | Constant store order differs -SHC_3BB0A8C1_0x004CA0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA0C0 | 82.3% | Constant store order differs -SHC_3BB0A8C1_0x004CA4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA4D0 | 74.9% | Constant store order differs -SHC_3BB0A8C1_0x004CA8A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA8A0 | 84.8% | Constant store order differs -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 | 85.3% | Register allocation differs -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 | 91.3% | 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 | 88.9% | Register allocation differs -SHC_3BB0A8C1_0x004CB9F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB9F0 | 93.3% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBA50 | 85.1% | Reimplemented functionally, stack and cached load differencesd -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 | 94.2% | Register scheduling differs SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC420 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC420 | 38.0% | Loop strength reduction diff -SHC_3BB0A8C1_0x004CC520 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC520 | 93.8% | Reimplemented -SHC_3BB0A8C1_0x004CC5A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC5A0 | 85.7% | Register allocation differs -SHC_3BB0A8C1_0x004CC680 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC680 | 83.3% | Register push placement diff 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 | 40.90% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CCAF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCAF0 | 68.1% | Register allocation differs -SHC_3BB0A8C1_0x004CCBE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCD20 | 63.9% | Loop register allocation diff SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending @@ -35265,175 +35265,175 @@ SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending SHC_3BB0A8C1_0x004CD070 | 0.0% | Pending -SHC_3BB0A8C1_0x004CD0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD160 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD160 | 91.7% | Register allocation differs -SHC_3BB0A8C1_0x004CD250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD250 | 94.7% | Reimplemented -SHC_3BB0A8C1_0x004CD2A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD2A0 | 59.60% | Reimplemented functionally, code block ordering problem -SHC_3BB0A8C1_0x004CD560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CD690 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented -SHC_3BB0A8C1_0x004CD7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD7B0 | 76.1% | Register allocation differs -SHC_3BB0A8C1_0x004CD8F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD8F0 | 74.2% | Register allocation differs -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 | 23.3% | Loop/register structure differs -SHC_3BB0A8C1_0x004CDBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDBC0 | 65.0% | Loop counter spilled in orig -SHC_3BB0A8C1_0x004CDCA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCF0 | 41.8% | Register allocation differs -SHC_3BB0A8C1_0x004CDE60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDE60 | 76.1% | AIC index folding differs -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 | 55.3% | Register/store scheduling diff 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 | 87.0% | Register allocation differs -SHC_3BB0A8C1_0x004CEA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEA50 | 92.7% | Reimplemented functionally, stack ordering problem -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 | 62.2% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF2E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF2E0 | 46.8% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF400 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF470 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF470 | 56.0% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF4F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF4F0 | 57.10% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF560 | 37.74% | Reimplemented functionally, different register usage -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 | 62.22% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CF750 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF750 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -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 | 56.0% | Unresolved attackInfo offsets -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 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004D0520 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0580 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0580 | 95.7% | Reimplemented -SHC_3BB0A8C1_0x004D05D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D05D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0720 | 0.0% | Pending -SHC_3BB0A8C1_0x004D07C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D07C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0830 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D08B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D08B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0910 | 0.0% | Pending -SHC_3BB0A8C1_0x004D0A20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0A80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0B10 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B10 | 68.0% | Load scheduling differs -SHC_3BB0A8C1_0x004D0B70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0BB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0BB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0D40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0D40 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0DB0 | 0.0% | Pending @@ -35447,103 +35447,103 @@ SHC_3BB0A8C1_0x004D0EF0 | 0.0% | Pending SHC_3BB0A8C1_0x004D0F40 | 0.0% | Pending -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 | 69.88% | Partially reimplemented, stuck SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending SHC_3BB0A8C1_0x004D1710 | 0.0% | Pending -SHC_3BB0A8C1_0x004D18B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D18B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1950 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004D1A00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs -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 | 29.4% | Register allocation differs 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 | 59.6% | Branch layout differs -SHC_3BB0A8C1_0x004D2730 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2730 | 79.2% | AIC index folding differs -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 | 73.0% | Register allocation differs -SHC_3BB0A8C1_0x004D2A70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2A70 | 30.0% | Register allocation differs -SHC_3BB0A8C1_0x004D2E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs -SHC_3BB0A8C1_0x004D2F80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D30E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented, barring one register usage difference -SHC_3BB0A8C1_0x004D3320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effectively 100% -SHC_3BB0A8C1_0x004D3780 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3780 | 74.0% | Constant 1 kept in register -SHC_3BB0A8C1_0x004D3850 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3850 | 82.3% | AIC index folding differs -SHC_3BB0A8C1_0x004D38C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D38C0 | 83.8% | AIC index folding differs SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3960 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D39B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3AE0 | 35.8% | Block layout differs -SHC_3BB0A8C1_0x004D3F20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented -SHC_3BB0A8C1_0x004D4040 | 100.0% | Reimplemented, for loop didn't work +SHC_3BB0A8C1_0x004D4040 | 97.0% | Reimplemented -SHC_3BB0A8C1_0x004D40B0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D40B0 | 94.9% | Reimplemented SHC_3BB0A8C1_0x004D4130 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4220 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4220 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4340 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4340 | 54.63% | Reimplemented, different register usage SHC_3BB0A8C1_0x004D4400 | 0.0% | Pending -SHC_3BB0A8C1_0x004D44E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D44E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4530 | 0.0% | Pending @@ -35551,15 +35551,15 @@ SHC_3BB0A8C1_0x004D45E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4630 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4680 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4680 | 64.80% | Stuck, register usage SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending -SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs -SHC_3BB0A8C1_0x004D4940 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D49E0 | 52.84% | Register allocation differences, logic checks out SHC_3BB0A8C1_0x004D5010 | 100.0% | Reimplemented @@ -35571,7 +35571,7 @@ SHC_3BB0A8C1_0x004D5370 | 0.0% | Pending SHC_3BB0A8C1_0x004D53A0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D53E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D53E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D55D0 | 0.0% | Pending