diff --git a/src/OpenSHC/AI/AIVState/aiDecideOnNewBuildings.cpp b/src/OpenSHC/AI/AIVState/aiDecideOnNewBuildings.cpp new file mode 100644 index 000000000..912593888 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiDecideOnNewBuildings.cpp @@ -0,0 +1,115 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/AI/AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_AICState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004F15C0 + void AIVState::aiDecideOnNewBuildings(int playerID) + { + int const aivID = DAT_GameState::instance.playerDataArray[playerID].aivID; + int isPausing = 0; + + if (aivID <= 0) { + return; + } + + int stepIncrement = 1; + + if (DAT_GameState::instance.playerDataArray[playerID].resourceRebuildDelay != 0) { + ++DAT_GameState::instance.playerDataArray[playerID].resourceRebuildDelay; + if (DAT_GameState::instance.playerDataArray[playerID].resourceRebuildDelay + < MACRO_CALL_MEMBER(AICState_Func::getResourceRebuildDelay, DAT_AICState::ptr)(playerID)) { + return; + } + DAT_GameState::instance.playerDataArray[playerID].resourceRebuildDelay = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] > 16000) { + // rich + stepIncrement = 2; + } else if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + > 5000) { + stepIncrement = 1; + } else { + // poor + ++this->aivs[aivID].aivPoorCounter; + if (this->aivs[aivID].aivPoorCounter < this->aivs[aivID].aivPoorLimit_OR_AIC_buildInterval) { + return; + } + } + this->aivs[aivID].aivPoorCounter = 0; + + ++DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator; + if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator >= 5) { + DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator == 0) { + if (!MACRO_CALL_MEMBER(AICState_Func::shouldDestroyWoodCutterUnk, DAT_AICState::ptr)(playerID) + && MACRO_CALL_MEMBER(AICState_Func::aiShouldBuildWoodcutter, DAT_AICState::ptr)(playerID)) { + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceWoodcuttershut, this)(playerID); + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator == 1) { + if (MACRO_CALL_MEMBER(AICState_Func::aiShouldBuildFarm, DAT_AICState::ptr)(playerID) + && MACRO_CALL_MEMBER(AIVState_Func::aiPlaceFarm, this)(playerID, + (MappersEnum)MACRO_CALL_MEMBER(AICState_Func::aiFarmChoice, DAT_AICState::ptr)(playerID))) { + MACRO_CALL_MEMBER(AICState_Func::setNextFarmTypeToBuild, DAT_AICState::ptr)(playerID); + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator == 2) { + if (MACRO_CALL_MEMBER(AICState_Func::aiShouldBuildQuarry, DAT_AICState::ptr)(playerID)) { + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceQuarryAndOxtether, this)(playerID); + } + if (MACRO_CALL_MEMBER(AICState_Func::aiRequiresExtraOxtethers, DAT_AICState::ptr)(playerID)) { + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceAdditionalOxtether, this)(playerID); + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator == 3) { + if (MACRO_CALL_MEMBER(AICState_Func::aiShouldBuildIronMine, DAT_AICState::ptr)(playerID)) { + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceIronMine, this)(playerID); + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiResourceBuildingFocusIndicator == 4) { + if (MACRO_CALL_MEMBER(AICState_Func::aiShouldBuildPitchrig, DAT_AICState::ptr)(playerID)) { + MACRO_CALL_MEMBER(AIVState_Func::aiPlacePitchrig, this)(playerID); + } + } + + if (DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause != 0) { + if (stepIncrement > 1) { + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause = 0; + } else { + --DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause; + } + isPausing = 1; + } else { + int const pauseStep + = DAT_GameState::instance.playerDataArray[playerID] + .aivPauses[DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex]; + if (pauseStep > 0 && this->aivs[aivID].currentStepGoal >= pauseStep) { + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause + = DAT_GameState::instance.playerDataArray[playerID].aivPauseDelay; + ++DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex; + return; + } + // the step goal can advance by 2 when the player is rich + this->aivs[aivID].currentStepGoal += stepIncrement; + } + + if (this->aivs[aivID].aivSubType == 0) { + MACRO_CALL_MEMBER(AIVState_Func::executeDefaultCastleAIV, this)(playerID, isPausing); + } else if (!MACRO_CALL_MEMBER(AICState_Func::destroyHouse, DAT_AICState::ptr)(playerID)) { + for (int step = 1; step <= this->aivs[aivID].totalSteps && step <= this->aivs[aivID].currentStepGoal; + ++step) { + if (MACRO_CALL_MEMBER(AIVState_Func::aiPlaceAIVBuilding, this)(playerID, step, isPausing)) { + break; + } + } + } + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceFlag, this)(playerID); + MACRO_CALL_MEMBER(AIVState_Func::aiPlaceBrazier, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp b/src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp index a4f565788..5e947a8bf 100644 --- a/src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp +++ b/src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp @@ -297,7 +297,7 @@ namespace AI { orientation); int const placedID = DAT_TileMapState::instance.placedBuildingID; if (!DAT_TileMapState::instance.buildingPlacementFail) { - DAT_BuildingsState::instance.buildings[placedID].field240_0x2c8 = 1; + DAT_BuildingsState::instance.buildings[placedID].field202_0x2c8 = 1; MACRO_CALL_MEMBER(AICState_Func::setupWorkshopProductionType, DAT_AICState::ptr)(playerID, placedID); } if (!DAT_TileMapState::instance.buildingPlacementFail) { diff --git a/src/OpenSHC/AI/AIVState/aiPlaceFlag.cpp b/src/OpenSHC/AI/AIVState/aiPlaceFlag.cpp new file mode 100644 index 000000000..29d096362 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceFlag.cpp @@ -0,0 +1,54 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Map/Entities/EntityState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" + +#include "OpenSHC/Globals/DAT_AICState.hpp" +#include "OpenSHC/Globals/DAT_EntityState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ED240 + void AIVState::aiPlaceFlag(int playerID) + { + Map::Entities::EntityType const flagType + = (Map::Entities::EntityType)DAT_AICState::instance + .aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1] + .flagType; + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[AIVUT_FLAG] <= 0) { + return; + } + for (int i = 0; i < 10; ++i) { + int const tile = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[AIVUT_FLAG][i]; + if (tile <= 0) { + continue; + } + int const entityID = DAT_TileMapState::instance.EntityLayer[tile]; + if (entityID != 0 && DAT_EntityState::instance.entityArray[entityID].entityType == flagType) { + continue; + } + int const height + = MACRO_CALL_MEMBER(Map::TileMapState_Func::getTotalHeightAt, DAT_TileMapState::ptr)(tile, playerID); + if (height <= 0) { + continue; + } + MACRO_CALL_MEMBER(Map::Entities::EntityState_Func::spawnProjectileEntity, DAT_EntityState::ptr)(0, playerID, + playerID, + (tile + - DAT_ViewportRenderState::instance + .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] + .addXgetTile) + * 8 + + 4, + DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile] * 8 + 4, height, 0, 0, 0, + flagType, 0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp b/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp new file mode 100644 index 000000000..801074065 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp @@ -0,0 +1,44 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EFAB0 + void AIVState::aiPlaceWoodcuttershut(int playerID) + { + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_WOODSMAN, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return; + } + MACRO_CALL_MEMBER(AIVState_Func::findAppropriateWoodCutterGridLocation, this)(playerID); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + this->heatMaps[(uint)this->buildingApproriateGridXPosition][(uint)this->buildingAppropriateGridYPosition] + .woodRelatedCountdownTo0 = 4; + // search around the result of the woodcutter search + MACRO_CALL_MEMBER(AIVState_Func::findSuitableGridLocationFittingCriteria, this)( + this->buildingApproriateGridXPosition, this->buildingAppropriateGridYPosition); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + ++this->heatMaps[(uint)this->buildingApproriateGridXPosition][(uint)this->buildingAppropriateGridYPosition] + .structureCount; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + this->buildingApproriateGridXPosition * 5, this->buildingAppropriateGridYPosition * 5, + Commands::M_MAPPER_WOODSMAN, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_WOODSMAN), + 15); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/applyAIV.cpp b/src/OpenSHC/AI/AIVState/applyAIV.cpp new file mode 100644 index 000000000..03cb7b5a4 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/applyAIV.cpp @@ -0,0 +1,192 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/FilePackager.func.hpp" +#include "OpenSHC/Rendering/ViewportRenderState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.hpp" +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" +#include "OpenSHC/Globals/FilePackagerObj.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EF0D0 + void AIVState::applyAIV(int aivID, int playerID) + { + BOOLEnum keepDefined = FALSE; + MACRO_CALL_MEMBER(IO::FilePackager_Func::unpackAIVFile, FilePackagerObj::ptr)( + DAT_AIVDefinedData::instance.AIVSectionAddressArray, + DAT_AIVDefinedData::instance.AIVFileNames[this->aivs[aivID].aiType - 2][this->aivs[aivID].castleID]); + MACRO_CALL_MEMBER(AIVState_Func::rotateAIV, this)(this->aivs[aivID].keepOrientation); + + // single buildings and the keep, steps is accessed as a flat 100 * 100 array + int x; + int y = 0; + int index = 0; + for (; y < 100; ++y) { + for (x = 0; x < 100; ++x, ++index) { + if (this->isHandled[index]) { + continue; + } + int const type = this->constructions[index]; + if (type == 0) { + this->isHandled[index] = 1; + } else if (type == 2) { + this->isHandled[index] = 1; + } else if (type == AIV::AIVBT_KEEP2) { + if (!keepDefined) { + this->keepX = this->aivs[aivID].keepXOffset + x; + this->keepY = this->aivs[aivID].keepYOffset + y; + keepDefined = TRUE; + } + this->isHandled[index] = 1; + } else { + MappersEnum const mapper = MACRO_CALL_MEMBER( + AIVState_Func::convertAIVBuildingTypeToCommandBuildingType, this)((AIV::AIVBuildingType2)type); + int const step = this->steps[0][index]; + // walls, moats and pitch ditches are collected below + if (mapper == Commands::M_MAPPER_WALL || mapper == Commands::M_MAPPER_WOODWALL + || mapper == Commands::M_MAPPER_CRENAL || mapper == Commands::M_MAPPER_CRENAL2 + || mapper == Commands::M_MAPPER_MOAT || mapper == Commands::M_MAPPER_PITCH_DITCH) { + continue; + } + this->isHandled[index] = 1; + if (this->aivs[aivID].aivBuildingSteps[step].quantity > 0) { + continue; + } + int const realY = this->aivs[aivID].keepYOffset + y; + int const realX = this->aivs[aivID].keepXOffset + x; + if (!MACRO_CALL_MEMBER(Rendering::ViewportRenderState_Func::xyAreValid, + DAT_ViewportRenderState::ptr)(realX, this->aivs[aivID].keepYOffset + y)) { + continue; + } + int const tile = DAT_ViewportRenderState::instance.translationMatrix[realY].addXgetTile + + this->aivs[aivID].keepXOffset + x; + this->aivs[aivID].aivBuildingSteps[step].buildingType = mapper; + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_unbuilt; + this->aivs[aivID].aivBuildingSteps[step].quantity = 1; + this->aivs[aivID].aivBuildingSteps[step].location.tile.tile = tile; + if (step > this->aivs[aivID].totalSteps) { + this->aivs[aivID].totalSteps = step; + } + } + } + } + + // collect the tiles of each wall, moat and pitch ditch step, one step per pass + int startY = 0; + BOOLEnum selected; + do { + selected = FALSE; + index = startY * 100; + int step; + MappersEnum mapper; + for (y = startY; y < 100; ++y) { + for (x = 0; x < 100; ++x, ++index) { + if (this->isHandled[index]) { + continue; + } + if (!selected) { + mapper = MACRO_CALL_MEMBER(AIVState_Func::convertAIVBuildingTypeToCommandBuildingType, this)( + (AIV::AIVBuildingType2)this->constructions[index]); + step = this->steps[0][index]; + this->aivs[aivID].aivBuildingSteps[step].location.tile.tile + = this->aivs[aivID].wallLocationsArrayIndex; + selected = TRUE; + if (y > startY) { + startY = y; + } + } else if (this->steps[0][index] != step) { + continue; + } + int const realY = this->aivs[aivID].keepYOffset + y; + int const realX = this->aivs[aivID].keepXOffset + x; + if (realX < 0 || realX > 399) { + this->isHandled[index] = 1; + continue; + } + if (realY < 0 || realY > 399) { + this->isHandled[index] = 1; + continue; + } + if (!DAT_ViewportRenderState::instance.DAT_BinaryTileMap400x400[realY * 400 + realX]) { + this->isHandled[index] = 1; + continue; + } + int const tile + = DAT_ViewportRenderState::instance.translationMatrix[this->aivs[aivID].keepYOffset + y] + .addXgetTile + + this->aivs[aivID].keepXOffset + x; + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_unbuilt; + this->aivs[aivID].aivBuildingSteps[step].buildingType = mapper; + ++this->aivs[aivID].aivBuildingSteps[step].quantity; + this->aivs[aivID].locationsArray[this->aivs[aivID].wallLocationsArrayIndex] = tile; + ++this->aivs[aivID].wallLocationsArrayIndex; + if (step > this->aivs[aivID].totalSteps) { + this->aivs[aivID].totalSteps = step; + } + this->isHandled[index] = 1; + } + } + } while (selected); + + // rotate the unit locations and copy them to the player + for (int unitType = 0; unitType < 22; ++unitType) { + DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType] = 0; + if (unitType == AIVUT_PIKEMAN || unitType == AIVUT_SWORDSMAN || unitType == AIVUT_ARABIAN_SWORDSMAN) { + continue; + } + for (int i = 0; i < 10; ++i) { + int const location = DAT_AIVState::instance.unitLocations[unitType][i]; + if (location <= 0) { + continue; + } + int const relativeY = location / 100; + int const relativeX = location % 100; + if (this->aivs[aivID].keepOrientation == 0) { + x = relativeX; + y = relativeY; + } else if (this->aivs[aivID].keepOrientation == 4) { + x = 99 - relativeX; + y = 99 - relativeY; + } else if (this->aivs[aivID].keepOrientation == 6) { + x = 99 - relativeY; + y = relativeX; + } else if (this->aivs[aivID].keepOrientation == 2) { + x = relativeY; + y = 99 - relativeX; + } + DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[unitType][i] + = DAT_ViewportRenderState::instance.translationMatrix[this->aivs[aivID].keepYOffset + y].addXgetTile + + this->aivs[aivID].keepXOffset + x; + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[unitType][i] > 0) { + ++DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + } + } + } + + for (int i = 0; i < 20; ++i) { + DAT_GameState::instance.playerDataArray[playerID].aivPauses[i] = (short)DAT_AIVState::instance.pauses[i]; + } + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex = 1; + if (DAT_AIVState::instance.pauseDelay <= 0) { + DAT_AIVState::instance.pauseDelay = 100; + } + DAT_GameState::instance.playerDataArray[playerID].aivPauseDelay = DAT_AIVState::instance.pauseDelay; + + // steps that can not be built + for (int step = 1; step <= this->aivs[aivID].totalSteps; ++step) { + int const buildingType = this->aivs[aivID].aivBuildingSteps[step].buildingType; + if (buildingType == Commands::M_MAPPER_NULL || buildingType == Commands::M_MAPPER_KEEP3 + || buildingType == Commands::M_MAPPER_POND1 || buildingType == Commands::M_MAPPER_POND2_SMALL + || buildingType == Commands::M_MAPPER_POND3_LARGE1 || buildingType == Commands::M_MAPPER_POND4_LARGE2) { + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_disabled; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/computeLargestSeparateArea.cpp b/src/OpenSHC/AI/AIVState/computeLargestSeparateArea.cpp new file mode 100644 index 000000000..04d2df38a --- /dev/null +++ b/src/OpenSHC/AI/AIVState/computeLargestSeparateArea.cpp @@ -0,0 +1,36 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EDD00 + int AIVState::computeLargestSeparateArea() + { + int areaSizes[10000]; + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + sizeof(areaSizes), 0, areaSizes); + for (int tile = 0; tile < 80400; ++tile) { + int const areaID = (short)DAT_TileMapState::instance.PathConnectionLayer[tile]; + if (areaID > 0) { + ++areaSizes[areaID]; + } + } + + int highestSize = 0; + int largestAreaID = 0; + for (int areaID = 0; areaID < 10000; ++areaID) { + if (areaSizes[areaID] > highestSize) { + highestSize = areaSizes[areaID]; + largestAreaID = areaID; + } + } + return largestAreaID; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp b/src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp new file mode 100644 index 000000000..91c249303 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp @@ -0,0 +1,643 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/AI/AICState.func.hpp" +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_AICState.hpp" +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // builds a generic castle for AIs without an AIV, the number of each building depends on the AI character + // FUNCTION: STRONGHOLDCRUSADER 0x004F0060 + BOOLEnum AIVState::executeDefaultCastleAIV(int playerID, int isPausing) + { + int const aivID = DAT_GameState::instance.playerDataArray[playerID].aivID; + int stockpiles = 0; + int hovels = 0; + int granaries = 1; + int marketplaces = 1; + int armories = 0; + int barracks = 0; + int mercenaryPosts = 0; + int cathedrals = 0; + int chapels = 0; + int inns = 0; + int breweries = 0; + int bakeries = 0; + int mills = 0; + int poleturners = 0; + int fletchers = 0; + int tanners = 0; + int blacksmiths = 0; + int armourers = 0; + int engineersGuilds = 0; + int tunnelersGuilds = 0; + int stables = 0; + int const aiType = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (aiType == AITA_RAT) { + stockpiles = 0; + hovels = 3; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 1; + fletchers = 1; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 0; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_SNAKE) { + stockpiles = 2; + hovels = 4; + armories = 1; + barracks = 1; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 2; + fletchers = 2; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_PIG) { + stockpiles = 2; + hovels = 4; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 2; + tanners = 2; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_WOLF) { + stockpiles = 3; + hovels = 5; + armories = 1; + barracks = 1; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 2; + inns = 2; + breweries = 3; + bakeries = 0; + mills = 0; + poleturners = 2; + fletchers = 2; + tanners = 1; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 1; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_SALADIN) { + stockpiles = 3; + hovels = 8; + armories = 0; + barracks = 0; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 4; + mills = 2; + poleturners = 0; + fletchers = 0; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_CALIPH) { + stockpiles = 2; + hovels = 6; + armories = 0; + barracks = 0; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 2; + breweries = 5; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 0; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_SULTAN) { + stockpiles = 0; + hovels = 3; + armories = 0; + barracks = 0; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 0; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_RICHARD) { + stockpiles = 2; + hovels = 6; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 0; + chapels = 2; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 2; + fletchers = 2; + tanners = 0; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 2; + stables = 0; + tunnelersGuilds = 1; + } else if (aiType == AITA_FREDERICK) { + stockpiles = 3; + hovels = 7; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 1; + chapels = 0; + inns = 2; + breweries = 2; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 2; + tanners = 2; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 2; + stables = 2; + tunnelersGuilds = 0; + } else if (aiType == AITA_PHILIPP) { + stockpiles = 2; + hovels = 5; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 0; + chapels = 2; + inns = 0; + breweries = 0; + bakeries = 0; + mills = 0; + poleturners = 3; + fletchers = 2; + tanners = 0; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 2; + stables = 5; + tunnelersGuilds = 0; + } else if (aiType == AITA_WAZIR) { + stockpiles = 2; + hovels = 6; + armories = 0; + barracks = 0; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 0; + breweries = 0; + bakeries = 3; + mills = 1; + poleturners = 0; + fletchers = 0; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_EMIR) { + stockpiles = 3; + hovels = 7; + armories = 1; + barracks = 1; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 1; + breweries = 2; + bakeries = 2; + mills = 1; + poleturners = 0; + fletchers = 1; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_NIZAR) { + stockpiles = 2; + hovels = 8; + armories = 0; + barracks = 0; + mercenaryPosts = 1; + cathedrals = 0; + chapels = 0; + inns = 1; + breweries = 3; + bakeries = 4; + mills = 1; + poleturners = 0; + fletchers = 0; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_SHERIFF) { + stockpiles = 3; + hovels = 6; + armories = 1; + barracks = 1; + mercenaryPosts = 1; + cathedrals = 1; + chapels = 2; + inns = 2; + breweries = 2; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 2; + tanners = 2; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } else if (aiType == AITA_MARSHAL) { + stockpiles = 2; + hovels = 5; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 0; + chapels = 0; + inns = 2; + breweries = 3; + bakeries = 0; + mills = 0; + poleturners = 0; + fletchers = 2; + tanners = 0; + blacksmiths = 2; + engineersGuilds = 1; + armourers = 2; + stables = 4; + tunnelersGuilds = 0; + } else if (aiType == AITA_ABBOT) { + stockpiles = 2; + hovels = 6; + armories = 1; + barracks = 1; + mercenaryPosts = 0; + cathedrals = 1; + chapels = 5; + inns = 3; + breweries = 6; + bakeries = 4; + mills = 2; + poleturners = 0; + fletchers = 3; + tanners = 0; + blacksmiths = 0; + engineersGuilds = 1; + armourers = 0; + stables = 0; + tunnelersGuilds = 0; + } + + for (int step = 0; step < 50; ++step) { + if (step > this->aivs[aivID].currentStepGoal) { + return FALSE; + } + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_disabled) { + // use the step for the next building that is still missing + if (stockpiles > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_STORES; + } else if (hovels > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_HOVEL; + } else if (granaries > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_GRANARY; + } else if (marketplaces > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_TRADEPOST; + } else if (barracks > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_BARRACKS_ARAB; + } else if (mercenaryPosts > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_BARRACKS_EURO; + } else if (armories > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_ARMOURY; + } else if (breweries > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_BREWER; + } else if (bakeries > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_BAKER; + } else if (mills > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_MILL; + } else if (poleturners > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_POLETURNER; + } else if (fletchers > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_FLETCHER; + } else if (tanners > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_TANNER; + } else if (blacksmiths > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_BLACKSMITH; + } else if (armourers > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_ARMOURER; + } else if (chapels > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_CHURCH1; + } else if (inns > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_INN; + } else if (engineersGuilds > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_ENGINEERS_GUILD; + } else if (tunnelersGuilds > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_TUNNELERS_GUILD; + } else if (stables > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_STABLES; + } else if (cathedrals > 0) { + this->aivs[aivID].aivBuildingSteps[step].buildingType = Commands::M_MAPPER_CHURCH3; + } else { + continue; + } + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_unbuilt; + } + + AIVBuildingStepStatusByte const status = this->aivs[aivID].aivBuildingSteps[step].buildStatus; + if (status != AIVBSS_disabled) { + if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_HOVEL) { + --hovels; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_STORES) { + --stockpiles; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_GRANARY) { + --granaries; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_TRADEPOST) { + --marketplaces; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_BARRACKS_ARAB) { + --barracks; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_BARRACKS_EURO) { + --mercenaryPosts; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_ARMOURY) { + --armories; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_BREWER) { + --breweries; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_BAKER) { + --bakeries; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_MILL) { + --mills; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_POLETURNER) { + --poleturners; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_FLETCHER) { + --fletchers; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_TANNER) { + --tanners; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_BLACKSMITH) { + --blacksmiths; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_ARMOURER) { + --armourers; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_CHURCH1) { + --chapels; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_INN) { + --inns; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType + == Commands::M_MAPPER_ENGINEERS_GUILD) { + --engineersGuilds; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType + == Commands::M_MAPPER_TUNNELERS_GUILD) { + --tunnelersGuilds; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_STABLES) { + --stables; + } else if (this->aivs[aivID].aivBuildingSteps[step].buildingType == Commands::M_MAPPER_CHURCH3) { + --cathedrals; + } + } + + MappersEnum const mapper = (MappersEnum)this->aivs[aivID].aivBuildingSteps[step].buildingType; + if (isPausing && mapper != Commands::M_MAPPER_HOVEL) { + continue; + } + if (status == AIVBSS_built) { + // only rebuild buildings that were destroyed + int const buildingID = (short)DAT_TileMapState::instance + .BuildingLayer[this->aivs[aivID].aivBuildingSteps[step].location.tile.tile]; + if (buildingID != 0 + && (short)DAT_BuildingsState::instance.buildings[buildingID].buildingType + == MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::convertCommandBuildingTypeToBuildingType, + DAT_BuildingsState::ptr)(mapper)) { + continue; + } + } + if (DAT_GameState::instance.playerDataArray[playerID].closestEnemyDistanceToTheLord < 120) { + continue; + } + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(mapper, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + continue; + } + if (mapper == Commands::M_MAPPER_WOODSMAN) { + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) { + continue; + } + } else if (mapper == Commands::M_MAPPER_GRANARY + && !DAT_GameState::instance.playerDataArray[playerID].granaryIsAlmostFilledUp) { + continue; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBeBuildingArmoury, DAT_AICState::ptr)(playerID, mapper) + || MACRO_CALL_MEMBER(AICState_Func::shouldNotBeBuildingHovel, DAT_AICState::ptr)(playerID, mapper) + || MACRO_CALL_MEMBER(AICState_Func::shouldNotBuildMarketArmoryGranaryBarracksMercenarypost, + DAT_AICState::ptr)(playerID, mapper)) { + continue; + } + + int orientation = 15; + if (mapper == Commands::M_MAPPER_BARRACKS_ARAB || mapper == Commands::M_MAPPER_BARRACKS_EURO + || mapper == Commands::M_MAPPER_ENGINEERS_GUILD || mapper == Commands::M_MAPPER_TUNNELERS_GUILD + || mapper == Commands::M_MAPPER_OIL_SMELTER) { + orientation = this->aivs[aivID].keepOrientation; + } else if (mapper == Commands::M_MAPPER_POLETURNER || mapper == Commands::M_MAPPER_BLACKSMITH + || mapper == Commands::M_MAPPER_ARMOURER || mapper == Commands::M_MAPPER_TANNER + || mapper == Commands::M_MAPPER_FLETCHER || mapper == Commands::M_MAPPER_BAKER + || mapper == Commands::M_MAPPER_BREWER) { + orientation = 8; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldWaitWithBuildingAPositiveFearFactor, DAT_AICState::ptr)( + playerID, mapper) + || MACRO_CALL_MEMBER(AICState_Func::shouldWaitWithBuildingNegativeFearFactor, DAT_AICState::ptr)( + playerID, mapper)) { + continue; + } + if (orientation == 15) { + DAT_TileMapState::instance.DAT_TempBuildingRotation = 0; + } else { + DAT_TileMapState::instance.DAT_TempBuildingRotation = orientation / 2; + } + uint const buildingSize = MACRO_CALL_MEMBER( + Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)(mapper); + + uint x; + uint y; + if (mapper == Commands::M_MAPPER_STORES) { + // stockpiles are placed next to each other at the campground + if (stockpiles == 0) { + y = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry - 11; + x = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry + 9; + } else if (stockpiles == 1) { + y = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry - 11; + x = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry + 4; + } else if (stockpiles == 2) { + y = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry - 6; + x = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry + 9; + } + } else { + if (mapper == Commands::M_MAPPER_BARRACKS_ARAB || mapper == Commands::M_MAPPER_BARRACKS_EURO + || mapper == Commands::M_MAPPER_ENGINEERS_GUILD || mapper == Commands::M_MAPPER_TUNNELERS_GUILD + || mapper == Commands::M_MAPPER_CHURCH3) { + MACRO_CALL_MEMBER(AIVState_Func::findAppropriateGridLocationForBuilding, this)( + DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5, + DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5); + if (this->buildingApproriateGridXPosition == -1) { + continue; + } + } else { + MACRO_CALL_MEMBER(AIVState_Func::findSuitableGridLocationFittingCriteria, this)( + DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5, + DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5); + if (this->buildingApproriateGridXPosition == -1) { + continue; + } + } + if (this->buildingAppropriateGridYPosition == -1) { + continue; + } + x = this->buildingApproriateGridXPosition * 5; + y = this->buildingAppropriateGridYPosition * 5; + } + + int const tile = DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile + x; + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, x, y, mapper, buildingSize); + // keep orientation is 0, 2, 4 or 6, halving it gives the index of the part offsets + if (mapper == Commands::M_MAPPER_BARRACKS_EURO || mapper == Commands::M_MAPPER_BARRACKS_ARAB) { + // the three fields in front of the barracks + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][0].x + + x, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][0].y + + y, + mapper, buildingSize); + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][1].x + + x, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][1].y + + y, + mapper, buildingSize); + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][2].x + + x, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][2].y + + y, + mapper, buildingSize); + } else if (mapper == Commands::M_MAPPER_ENGINEERS_GUILD || mapper == Commands::M_MAPPER_TUNNELERS_GUILD) { + // the guild field + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][1].x + + x, + DAT_TerrainDefinedData::instance.BuildingPartsOffsets[this->aivs[aivID].keepOrientation / 2][1].y + + y, + mapper, buildingSize); + } else if (mapper == Commands::M_MAPPER_OIL_SMELTER) { + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, + DAT_TerrainDefinedData::instance.field63_0x19c[this->aivs[aivID].keepOrientation / 2].x + x, + DAT_TerrainDefinedData::instance.field63_0x19c[this->aivs[aivID].keepOrientation / 2].y + y, mapper, + buildingSize); + } + + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, x, y, mapper, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + mapper), + orientation); + int const placedID = DAT_TileMapState::instance.placedBuildingID; + if (!DAT_TileMapState::instance.buildingPlacementFail) { + DAT_BuildingsState::instance.buildings[placedID].field202_0x2c8 = 1; + MACRO_CALL_MEMBER(AICState_Func::setupWorkshopProductionType, DAT_AICState::ptr)(playerID, placedID); + this->aivs[aivID].aivBuildingSteps[step].location.tile.tile = tile; + if (!DAT_TileMapState::instance.buildingPlacementFail) { + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + } + } + this->aivs[aivID].aivBuildingSteps[step].wait + = this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built ? 10 : 16; + if (!DAT_TileMapState::instance.buildingPlacementFail) { + return TRUE; + } + } + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp b/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp new file mode 100644 index 000000000..132b3cd48 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp @@ -0,0 +1,87 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // breadth first search for a grid cell whose eight neighbours are all free + // FUNCTION: STRONGHOLDCRUSADER 0x004EEE10 + void AIVState::findAppropriateGridLocationForBuilding(uint gridX, uint gridY) + { + this->visitCount = 1; + this->algAIndex = 0; + this->algBIndex = 1; + this->heatMapXArray[0] = gridX; + this->heatMapYArray[0] = gridY; + this->buildingApproriateGridXPosition = -1; + this->buildingAppropriateGridYPosition = -1; + if (gridX > 79 || gridY > 79 || this->heatMaps[gridX][gridY].isNotNearEdgeUnk) { + return; + } + ++this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[gridX][gridY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[gridX][gridY].algorithmVisitCountUnk = this->visitCount; + + while (this->algAIndex != this->algBIndex) { + int freeCount = 0; + int const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + if (!this->heatMaps[x][y].tilesNotPartOfLargestAreaCount) { + freeCount = 1; + } + this->visitCount = (char)this->heatMaps[x][y].algorithmVisitCountUnk; + for (int direction = 0; direction < 8; ++direction) { + uint const candidateX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + uint const candidateY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.yOffset + y; + if (this->heatMaps[x][y].isNotNearEdgeUnk && (candidateX > 79 || candidateY > 79)) { + continue; + } + int const notInLargestArea + = (char)this->heatMaps[candidateX][candidateY].tilesNotPartOfLargestAreaCount; + if (!this->heatMaps[candidateX][candidateY].terrainDiffHigherThan12 + && !this->heatMaps[candidateX][candidateY].structureCount + && !this->heatMaps[candidateX][candidateY].isNotNearEdgeUnk + && !this->heatMaps[candidateX][candidateY].destructionBasedPlacementCooldown + && !this->heatMaps[candidateX][candidateY].treeCount + && (char)this->heatMaps[candidateX][candidateY].bouldersCount <= 0 && !notInLargestArea) { + ++freeCount; + } + if (this->heatMaps[candidateX][candidateY].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + this->heatMaps[candidateX][candidateY].algorithmIterationNumber + = this->mapExtraInfo.algorithmIterationNumber; + if (notInLargestArea >= 15) { + continue; + } + this->heatMaps[candidateX][candidateY].algorithmVisitCountUnk = this->visitCount + 1; + this->heatMapXArray[this->algBIndex] = candidateX; + this->heatMapYArray[this->algBIndex] = candidateY; + ++this->algBIndex; + if ((int)this->algBIndex >= 6400) { + this->algBIndex = 0; + } + } + if (freeCount == 9) { + // the result is the last visited neighbour + uint const resultY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[7].int_.yOffset + y; + uint const resultX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[7].int_.xOffset + x; + this->buildingApproriateGridXPosition = resultX; + this->buildingAppropriateGridYPosition = resultY; + return; + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/hostChecksLobbyAIVAvailability.cpp b/src/OpenSHC/AI/AIVState/hostChecksLobbyAIVAvailability.cpp new file mode 100644 index 000000000..8171ac18c --- /dev/null +++ b/src/OpenSHC/AI/AIVState/hostChecksLobbyAIVAvailability.cpp @@ -0,0 +1,127 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Synchrony/Actions.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/UI/Helpers.func.hpp" +#include "OpenSHC/UI/MenuModalComposition.func.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_MenuModalComposition1.hpp" +#include "OpenSHC/Globals/DAT_WindowAndDirectDraw.hpp" + +namespace OpenSHC { +namespace AI { + + // lists the AIs with an AIV that every player has, and removes other AIs from the lobby + // FUNCTION: STRONGHOLDCRUSADER 0x004ECBC0 + void AIVState::hostChecksLobbyAIVAvailability() + { + if (DAT_GameSynchronyState::instance.currentGameMode != Game::GM_SOLITARY + && DAT_GameSynchronyState::instance.currentGameMode != Game::GM_SKIRMISH_SINGLE_PLAYER) { + if (!DAT_GameSynchronyState::instance.isHost) { + return; + } + int const localPlayer = DAT_GameSynchronyState::instance.currentPlayerSlotID; + DAT_GameCore::instance.numOfAIsWithCastleUnk = 0; + for (int aiIndex = 0; aiIndex < 16; ++aiIndex) { + // the original walks this entry with its own pointer next to the received array + int const* const availability = &this->aivFileAvailabilityPerAIArray[aiIndex]; + BOOLEnum available = TRUE; + for (int player = 1; player < 9; ++player) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1) { + continue; + } + if (DAT_GameSynchronyState::instance.DAT_ReceivedAIVFileAvailabilityPerAIArray[player][0] == -1) { + // nothing received yet + if (player != localPlayer) { + continue; + } + if (*availability == 0) { + available = FALSE; + break; + } + } else if (player == localPlayer) { + if (*availability == 0) { + available = FALSE; + break; + } + } else if (DAT_GameSynchronyState::instance + .DAT_ReceivedAIVFileAvailabilityPerAIArray[player][aiIndex] + == 0) { + available = FALSE; + break; + } + } + if (available) { + DAT_GameCore::instance.arrayOfLordIdsWithAIVsUnk[DAT_GameCore::instance.numOfAIsWithCastleUnk] + = aiIndex + 1; + ++DAT_GameCore::instance.numOfAIsWithCastleUnk; + } + } + + BOOLEnum removedPlayer = FALSE; + for (int player = 1; player < 9; ++player) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] != -1 + || DAT_GameSynchronyState::instance.currentAIArray[player] == 0) { + continue; + } + // read through an alias: the original loads the AI ID again after the test above + int const& ai = DAT_GameSynchronyState::instance.currentAIArray[player]; + int const aiID = ai; + BOOLEnum hasAIV = FALSE; + for (int i = 0; i < DAT_GameCore::instance.numOfAIsWithCastleUnk; ++i) { + if (aiID == DAT_GameCore::instance.arrayOfLordIdsWithAIVsUnk[i]) { + hasAIV = TRUE; + break; + } + } + if (hasAIV) { + continue; + } + DAT_GameSynchronyState::instance.currentAIArray[player] = 0; + MACRO_CALL(Synchrony::Actions_Func::RemovePositionOfPlayer)(player); + removedPlayer = TRUE; + DAT_GameSynchronyState::instance.DAT_GameCommandParam0 = player; + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::queueCommand, DAT_GameSynchronyState::ptr)( + Commands::GCT_HOST_REMOVE_PLAYER_BY_SLOT); + } + if (removedPlayer) { + MACRO_CALL_MEMBER( + Synchrony::GameSynchronyState_Func::reorderTeamsAndPositions, DAT_GameSynchronyState::ptr)(); + DAT_GameSynchronyState::instance.reparseMaps = TRUE; + } + + // switch between the small and the extended AI selection + if (DAT_MenuModalComposition1::instance.activeModalDialogID == UI::Enums::MMT_BASIC_AI_LORD_SELECT) { + if (DAT_GameCore::instance.numOfAIsWithCastleUnk > 8) { + MACRO_CALL(UI::Helpers_Func::ClearLobbyHoveredAI)(); + MACRO_CALL_MEMBER(UI::MenuModalComposition_Func::setExtraActiveModalDialog, + DAT_MenuModalComposition1::ptr)(UI::Enums::MMT_EXTENDED_AI_LORD_SELECT, + DAT_WindowAndDirectDraw::instance.mainMenuBorderWidth + 4, + DAT_WindowAndDirectDraw::instance.mainMenuBorderHeight + 205); + } + } else if (DAT_MenuModalComposition1::instance.activeModalDialogID == UI::Enums::MMT_EXTENDED_AI_LORD_SELECT + && DAT_GameCore::instance.numOfAIsWithCastleUnk <= 8) { + MACRO_CALL(UI::Helpers_Func::ClearLobbyHoveredAI)(); + MACRO_CALL_MEMBER(UI::MenuModalComposition_Func::setExtraActiveModalDialog, + DAT_MenuModalComposition1::ptr)(UI::Enums::MMT_BASIC_AI_LORD_SELECT, + DAT_WindowAndDirectDraw::instance.mainMenuBorderWidth + 4, + DAT_WindowAndDirectDraw::instance.mainMenuBorderHeight + 205); + } + return; + } + + // single player + DAT_GameCore::instance.numOfAIsWithCastleUnk = this->numberOfAIsWithAIV; + int count = 0; + for (int aiIndex = 0; aiIndex < 16; ++aiIndex) { + if (this->aivFileAvailabilityPerAIArray[aiIndex] != 0) { + DAT_GameCore::instance.arrayOfLordIdsWithAIVsUnk[count] = aiIndex + 1; + ++count; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp b/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp new file mode 100644 index 000000000..073c7366c --- /dev/null +++ b/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp @@ -0,0 +1,184 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/LogicHelpers/Logic1.hpp" +#include "OpenSHC/Map/LogicHelpers/Logic2.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace Map::LogicHelpers; + + // recomputes the 5x5 tile heat map cells the AI uses to place buildings + // FUNCTION: STRONGHOLDCRUSADER 0x004F0E80 + void AIVState::recomputeHeatmaps(int initialize) + { + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) { + return; + } + + if (initialize) { + // full reset + MACRO_CALL_MEMBER(AIVState_Func::clearTheHeatmaps, this)(); + for (int y = 0; y < 80; ++y) { + for (int x = 0; x < 80; ++x) { + this->heatMaps[x][y].lowestTerrainHeight = 250; + this->heatMaps[x][y].isNotNearEdgeUnk = 1; + } + } + this->mapExtraInfo.largestSeparateArea + = MACRO_CALL_MEMBER(AIVState_Func::computeLargestSeparateArea, this)(); + } else { + for (int y = 0; y < 80; ++y) { + for (int x = 0; x < 80; ++x) { + this->heatMaps[x][y].treeCount = 0; + this->heatMaps[x][y].impassableCount = 0; + this->heatMaps[x][y].structureCount = 0; + this->heatMaps[x][y].tileOwnership = 0; + if ((char)this->heatMaps[x][y].woodRelatedCountdownTo0 > 0) { + --this->heatMaps[x][y].woodRelatedCountdownTo0; + } + if ((char)this->heatMaps[x][y].destructionBasedPlacementCooldown > 0) { + --this->heatMaps[x][y].destructionBasedPlacementCooldown; + } + } + } + } + + this->mapExtraInfo.totalWoodAvailable = 0; + if (this->mapExtraInfo.largestSeparateArea <= 0) { + return; + } + + if (initialize) { + for (int tile = 0; tile < 80400; ++tile) { + int const y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int const x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + int const gridY = y / 5; + int const gridX = x / 5; + if (gridX >= 1 && gridX <= 78 && gridY >= 1 && gridY <= 78) { + this->heatMaps[gridX][gridY].isNotNearEdgeUnk = 0; + } + if ((short)DAT_TileMapState::instance.PathConnectionLayer[tile] + != this->mapExtraInfo.largestSeparateArea) { + ++this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount; + } + if (DAT_TileMapState::instance.LogicLayer[tile] + & (L_SEA | L_BORDER | L_BORDER_EDGE | L_RIVER | L_FORD)) { + continue; + } + + int const height = DAT_TileMapState::instance.DefaultHeightLayer[tile]; + if (height < this->heatMaps[gridX][gridY].lowestTerrainHeight) { + this->heatMaps[gridX][gridY].lowestTerrainHeight = (byte)height; + } + if (height > this->heatMaps[gridX][gridY].highestTerrainHeight) { + this->heatMaps[gridX][gridY].highestTerrainHeight = (byte)height; + } + if ((int)this->heatMaps[gridX][gridY].highestTerrainHeight + > (int)this->heatMaps[gridX][gridY].lowestTerrainHeight + 12) { + this->heatMaps[gridX][gridY].terrainDiffHigherThan12 = 1; + } + + int const logic = DAT_TileMapState::instance.LogicLayer[tile]; + if (logic & L_TREE) { + int const treeID = (short)DAT_TileMapState::instance.OrganismLayer[tile]; + // tree types 1 to 4 give wood + if (treeID != 0 + && (DAT_LandscapeState::instance.trees[treeID].treeType == 1 + || DAT_LandscapeState::instance.trees[treeID].treeType == 2 + || DAT_LandscapeState::instance.trees[treeID].treeType == 3 + || DAT_LandscapeState::instance.trees[treeID].treeType == 4) + && DAT_LandscapeState::instance.trees[treeID].stage < 4 + && DAT_LandscapeState::instance.trees[treeID].state == 2 + && MACRO_CALL_MEMBER(Map::LandscapeState_Func::isTreeAdult, DAT_LandscapeState::ptr)( + treeID, DAT_LandscapeState::instance.trees[treeID].uid)) { + this->heatMaps[gridX][gridY].treeCount += 3; + this->mapExtraInfo.totalWoodAvailable += 3; + } + continue; + } + if (logic & L_BOULDERS) { + ++this->heatMaps[gridX][gridY].bouldersCount; + } else if (logic & L_IRON) { + ++this->heatMaps[gridX][gridY].ironCount; + } else if (logic & L_OIL) { + ++this->heatMaps[gridX][gridY].oilCount; + } else if (logic & L_MARSH) { + ++this->heatMaps[gridX][gridY].marshCount; + } else if (logic & (L_BUILDING | L_KEEP_NON_MANOR_HOUSE)) { + ++this->heatMaps[gridX][gridY].structureCount; + } else if (logic & L_WALL_OR_GATEHOUSE) { + ++this->heatMaps[gridX][gridY].structureCount; + } else if (logic & L_ROCKY) { + ++this->heatMaps[gridX][gridY].impassableCount; + } + if (!(DAT_TileMapState::instance.LogicLayer[tile] & L_RIVER)) { + if (DAT_TileMapState::instance.Logic2Layer[tile] & (L2_SCRUB | L2_OASIS_GRASS | L2_THICK_SCRUB)) { + ++this->heatMaps[gridX][gridY].oasisScrubCount; + } + if (DAT_TileMapState::instance.Logic2Layer[tile] & (L2_OASIS_GRASS | L2_THICK_SCRUB)) { + ++this->heatMaps[gridX][gridY].oasisThickScrubCount; + } + } + } + return; + } + + // only update what can change during the game + for (int tile = 0; tile < 80400; ++tile) { + if (!(DAT_TileMapState::instance.LogicLayer[tile] + & (L_ROCKY | L_WALL_OR_GATEHOUSE | L_BUILDING | L_TREE | L_KEEP_NON_MANOR_HOUSE))) { + continue; + } + if (DAT_TileMapState::instance.LogicLayer[tile] & L_ROCKY) { + int const y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int const gridX = (tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile) / 5; + int const gridY = y / 5; + ++this->heatMaps[gridX][gridY].impassableCount; + } + if (DAT_TileMapState::instance.LogicLayer[tile] & L_TREE) { + int const y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int const gridX = (tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile) / 5; + int const gridY = y / 5; + int const treeID = (short)DAT_TileMapState::instance.OrganismLayer[tile]; + // tree types 1 to 4 give wood + if (treeID != 0 + && (DAT_LandscapeState::instance.trees[treeID].treeType == 1 + || DAT_LandscapeState::instance.trees[treeID].treeType == 2 + || DAT_LandscapeState::instance.trees[treeID].treeType == 3 + || DAT_LandscapeState::instance.trees[treeID].treeType == 4) + && DAT_LandscapeState::instance.trees[treeID].stage < 4 + && DAT_LandscapeState::instance.trees[treeID].state == 2 + && MACRO_CALL_MEMBER(Map::LandscapeState_Func::isTreeAdult, DAT_LandscapeState::ptr)( + treeID, DAT_LandscapeState::instance.trees[treeID].uid)) { + this->heatMaps[gridX][gridY].treeCount += 3; + this->mapExtraInfo.totalWoodAvailable += 3; + } + } else if (DAT_TileMapState::instance.BuildingLayer[tile] != 0) { + int const y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int const gridX = (tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile) / 5; + int const gridY = y / 5; + ++this->heatMaps[gridX][gridY].structureCount; + this->heatMaps[gridX][gridY].tileOwnership + = (byte)DAT_BuildingsState::instance + .buildings[(short)DAT_TileMapState::instance.BuildingLayer[tile]] + .owner; + } else if (DAT_TileMapState::instance.LogicLayer[tile] & L_WALL_OR_GATEHOUSE) { + int const y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int const gridX = (tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile) / 5; + int const gridY = y / 5; + ++this->heatMaps[gridX][gridY].structureCount; + this->heatMaps[gridX][gridY].tileOwnership = (DAT_TileMapState::instance.WallOwnerLayer[tile] & 7) + 1; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/rotateAIV.cpp b/src/OpenSHC/AI/AIVState/rotateAIV.cpp new file mode 100644 index 000000000..61c3d3256 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/rotateAIV.cpp @@ -0,0 +1,46 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // rotates the loaded AIV grid to the keep orientation + // FUNCTION: STRONGHOLDCRUSADER 0x004ED0B0 + void AIVState::rotateAIV(int orientation) + { + if (orientation == 0) { + return; + } + if (orientation == 6) { + for (int y = 0; y < 100; ++y) { + for (int x = 0; x < 100; ++x) { + this->rotatedConstructions[x][99 - y] = this->constructions[y * 100 + x]; + this->rotatedSteps[x][99 - y] = this->steps[y][x]; + } + } + } else if (orientation == 4) { + for (int y = 0; y < 100; ++y) { + for (int x = 0; x < 100; ++x) { + this->rotatedConstructions[99 - y][99 - x] = this->constructions[y * 100 + x]; + this->rotatedSteps[99 - y][99 - x] = this->steps[y][x]; + } + } + } else if (orientation == 2) { + for (int y = 0; y < 100; ++y) { + for (int x = 0; x < 100; ++x) { + this->rotatedConstructions[99 - x][y] = this->constructions[y * 100 + x]; + this->rotatedSteps[99 - x][y] = this->steps[y][x]; + } + } + } + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::copyData, DAT_LowLevelMemory::ptr)( + sizeof(this->rotatedConstructions), this->rotatedConstructions, this->constructions); + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::copyData, DAT_LowLevelMemory::ptr)( + sizeof(this->rotatedSteps), this->rotatedSteps, this->steps); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp b/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp new file mode 100644 index 000000000..4c77c23e1 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp @@ -0,0 +1,109 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/AI/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/string-literals.hpp" + +#include "OpenSHC/Globals/DAT_AICState.hpp" +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SFXState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004F1860 + void AIVState::updateBuildingsStateAndUpdateAIBuildingDecisions() + { + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) { + return; + } + + if (DAT_GameState::instance.mapAndTime.startOfDay) { + this->counter = 0; + ++this->mapExtraInfo.algBuildingPlacementNudge; + if (this->mapExtraInfo.algBuildingPlacementNudge >= 31) { + this->mapExtraInfo.algBuildingPlacementNudge = 0; + } + for (int player = 1; player < 9; ++player) { + // the original tests the slot ID a third time through an alias of the same element + int const& fullID = DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player]; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1 + && DAT_GameSynchronyState::instance.currentAIArray[player] == 0 + && DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == fullID) { + continue; + } + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(player)) { + DAT_GameState::instance.playerDataArray[player].keepEnclosementRelatedCountdown = 5; + continue; + } + // warn the local player when he broke into an enemy castle + if (DAT_GameState::instance.playerDataArray[player].keepEnclosementRelatedCountdown > 0 + && --DAT_GameState::instance.playerDataArray[player].keepEnclosementRelatedCountdown == 0 + && DAT_GameState::instance.playerDataArray[player].defensesDamagedByPlayer + == DAT_GameSynchronyState::instance.currentPlayerSlotID + && DAT_GameState::instance.mapAndTime.playerTeams[player] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID] + && DAT_GameCore::instance.genieVoiceActive) { + // "Once more unto the breach" + MACRO_CALL_MEMBER(Audio::SFX::SFXState_Func::playWAVSFX, DAT_SFXState::ptr)( + s_Genie_35_wav_005ab8b8); + } + } + } + + if (DAT_GameState::instance.mapAndTime.monthChanged) { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::recomputePlayerDataStocksBasedOnStorageBuildings, + DAT_BuildingsState::ptr)(); + } + MACRO_CALL_MEMBER(AICState_Func::updateAIBehaviour, DAT_AICState::ptr)(); + if (DAT_GameState::instance.mapAndTime.weekChanged) { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::updatePathLinkageTileMap, DAT_BuildingsState::ptr)( + 2); + for (int player = 1; player < 9; ++player) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1 + && DAT_GameSynchronyState::instance.currentAIArray[player] != 0 + && DAT_GameState::instance.playerDataArray[player].playerDeathRelated == 0 + && DAT_GameState::instance.playerDataArray[player].lordKilledByPlayerID == 0 + && DAT_GameState::instance.playerDataArray[player].someCount31 == 0) { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::updateHeatmapBasedOnBuildingAccessibility, + DAT_BuildingsState::ptr)(player); + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::makeUnitsDisappearThatCannotReachTheKeep, + DAT_UnitsState::ptr)(player); + } + } + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::updatePathLinkageTileMap, DAT_BuildingsState::ptr)( + 0); + } + + // each AI player decides on its own tick of the day + for (int player = 1; player < 9; ++player) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1 + && DAT_GameSynchronyState::instance.currentAIArray[player] != 0 + && DAT_GameState::instance.playerDataArray[player].playerDeathRelated == 0 + && DAT_GameState::instance.playerDataArray[player].lordKilledByPlayerID == 0 + && DAT_GameState::instance.mapAndTime.dayTicks == player) { + MACRO_CALL_MEMBER(AIVState_Func::aiDecideOnNewBuildings, this)(player); + MACRO_CALL_MEMBER(AICState_Func::planToBuyWhenLowOnResourceAndSnoozeBuildings, DAT_AICState::ptr)( + player); + } + } + if (DAT_GameState::instance.mapAndTime.dayTicks == 8) { + MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::applySnoozedStateBasedOnPlayerData, DAT_BuildingsState::ptr)(); + } + if (DAT_GameState::instance.mapAndTime.monthTicks == 10) { + MACRO_CALL_MEMBER(AIVState_Func::recomputeHeatmaps, DAT_AIVState::ptr)(0); + } + } + +} +}