diff --git a/cmake/compiler-flags-gl.txt b/cmake/compiler-flags-gl.txt index 848d70de6..d312aa04a 100644 --- a/cmake/compiler-flags-gl.txt +++ b/cmake/compiler-flags-gl.txt @@ -7,3 +7,6 @@ src/OpenSHC/IO/ResourceManager/swapMapDataWithNextMap.cpp src/OpenSHC/UI/Rendering/WindowAndDirectDraw/takeScreenshot.cpp src/OpenSHC/IO/ResourceManager/readFirstPartOfCurrentResourceIntoMemory.cpp src/OpenSHC/IO/ResourceManager/readNextPartOfCurrentResourceIntoMemory.cpp + +src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp +src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp diff --git a/src/OpenSHC/AI/AIVState.func.hpp b/src/OpenSHC/AI/AIVState.func.hpp index 52040ac8d..0eaf7ba9a 100644 --- a/src/OpenSHC/AI/AIVState.func.hpp +++ b/src/OpenSHC/AI/AIVState.func.hpp @@ -72,7 +72,7 @@ namespace AI { aiPlaceAIVBuilding; MACRO_FUNCTION_RESOLVER( - void (AIVState::*)(), false, Address::SHC_3BB0A8C1_0x004EDC90, &AIVState::clearTheHeatmaps) + void (AIVState::*)(), true, Address::SHC_3BB0A8C1_0x004EDC90, &AIVState::clearTheHeatmaps) clearTheHeatmaps; MACRO_FUNCTION_RESOLVER( @@ -103,7 +103,7 @@ namespace AI { &AIVState::findSuitableGridLocationFittingCriteria) findSuitableGridLocationFittingCriteria; - MACRO_FUNCTION_RESOLVER(void (AIVState::*)(uint, uint), false, Address::SHC_3BB0A8C1_0x004EEE10, + MACRO_FUNCTION_RESOLVER(void (AIVState::*)(uint, uint), true, Address::SHC_3BB0A8C1_0x004EEE10, &AIVState::findAppropriateGridLocationForBuilding) findAppropriateGridLocationForBuilding; diff --git a/src/OpenSHC/AI/AIVState/Constructor_AIVState.cpp b/src/OpenSHC/AI/AIVState/Constructor_AIVState.cpp new file mode 100644 index 000000000..6e2dd4d6c --- /dev/null +++ b/src/OpenSHC/AI/AIVState/Constructor_AIVState.cpp @@ -0,0 +1,14 @@ +#include "../AIVState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004F13E0 + AIVState* AIVState::Constructor_AIVState() + { + MACRO_CALL_MEMBER(AIVState_Func::wipeAIVsAndHeatMaps, this)(); + return this; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiDecideOnNewBuildings.cpp b/src/OpenSHC/AI/AIVState/aiDecideOnNewBuildings.cpp new file mode 100644 index 000000000..a0c2c6e69 --- /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(PlayerID playerID) + { + int const aivID = DAT_GameState::instance.playerDataArray[playerID].aivID; + + if (aivID <= 0) { + return; + } + + int isPausing = 0; + 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 new file mode 100644 index 000000000..920fcbd29 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp @@ -0,0 +1,320 @@ +#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/Globals/DAT_AICState.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 { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ED410 + BOOLEnum AIVState::aiPlaceAIVBuilding(PlayerID playerID, int step, int isPausing) + { + int const aivID = DAT_GameState::instance.playerDataArray[playerID].aivID; + int x; + int y; + MappersEnum const mapper = (MappersEnum)this->aivs[aivID].aivBuildingSteps[step].buildingType; + + // when pausing, only hovels are built + if (isPausing && mapper != Commands::M_MAPPER_HOVEL) { + return FALSE; + } + // disabled steps and steps without room are never built + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_disabled) { + return FALSE; + } + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_insufficient_roomUnk) { + return FALSE; + } + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_insufficient_resources + && this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + // wait for more resources + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built) { + if (DAT_GameState::instance.playerDataArray[playerID].closestEnemyDistanceToTheLord < 120) { + return FALSE; + } + } else if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(mapper, playerID, FALSE)) { + return FALSE; + } + if (DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::mapperIsWoodCuttersHutWhileNoWoodAvailable, DAT_AICState::ptr)( + playerID, mapper)) { + return FALSE; + } + + if (mapper == Commands::M_MAPPER_MOAT) { + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built + && this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + for (int i = 0; i < this->aivs[aivID].aivBuildingSteps[step].quantity; ++i) { + int const tile + = this->aivs[aivID].locationsArray[this->aivs[aivID].aivBuildingSteps[step].location.tile.tile + i]; + MACRO_CALL_MEMBER(Map::TileMapState_Func::planMoat, DAT_TileMapState::ptr)( + playerID, tile, DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]); + } + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + this->aivs[aivID].aivBuildingSteps[step].wait = 50; + return FALSE; + } + + if (mapper == Commands::M_MAPPER_PITCH_DITCH) { + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built) { + if (this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_PITCH_DITCH, playerID, FALSE)) { + return FALSE; + } + } + for (int i = 0; i < this->aivs[aivID].aivBuildingSteps[step].quantity; ++i) { + int const tile + = this->aivs[aivID].locationsArray[this->aivs[aivID].aivBuildingSteps[step].location.tile.tile + i]; + y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, x, y, + Commands::M_MAPPER_PITCH_DITCH, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, + DAT_TileMapState::ptr)(Commands::M_MAPPER_PITCH_DITCH), + 15); + } + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + this->aivs[aivID].aivBuildingSteps[step].wait = 200; + return FALSE; + } + + // walls + if (mapper == Commands::M_MAPPER_WALL || mapper == Commands::M_MAPPER_WOODWALL + || mapper == Commands::M_MAPPER_CRENAL || mapper == Commands::M_MAPPER_CRENAL2) { + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built) { + if (this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_STONE] + <= 0) { + // no stone, request some + if (DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[Game::Resources::RT_STONE] + > 0) { + return FALSE; + } + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_STONE] + = 5; + return FALSE; + } + } + for (int i = 0; i < this->aivs[aivID].aivBuildingSteps[step].quantity; ++i) { + int const tile + = this->aivs[aivID].locationsArray[this->aivs[aivID].aivBuildingSteps[step].location.tile.tile + i]; + y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, x, y, mapper, 1); + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeDefensiveStructureTile, DAT_TileMapState::ptr)( + playerID, x, y, mapper); + } + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + this->aivs[aivID].aivBuildingSteps[step].wait = 20; + if (!DAT_TileMapState::instance.illegalBuild) { + return TRUE; + } + } else if (mapper == Commands::M_MAPPER_STAIR1 || mapper == Commands::M_MAPPER_STAIR2 + || mapper == Commands::M_MAPPER_STAIR3 || mapper == Commands::M_MAPPER_STAIR4 + || mapper == Commands::M_MAPPER_STAIR5 || mapper == Commands::M_MAPPER_STAIR6) { + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built + && this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + y = DAT_ViewportRenderState::instance + .tileTranslationMatrix_YComponent[this->aivs[aivID].aivBuildingSteps[step].location.tile.tile]; + x = this->aivs[aivID].aivBuildingSteps[step].location.tile.tile + - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(Map::TileMapState_Func::prepareAreaForBuildingPlacement, DAT_TileMapState::ptr)( + playerID, x, y, mapper, 1); + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeDefensiveStructureTile, DAT_TileMapState::ptr)( + playerID, x, y, mapper); + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + this->aivs[aivID].aivBuildingSteps[step].wait = 1; + if (!DAT_TileMapState::instance.illegalBuild) { + return TRUE; + } + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_insufficient_resources; + this->aivs[aivID].aivBuildingSteps[step].wait = 4; + } else { + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBeBuildingGranary, DAT_AICState::ptr)(playerID, mapper)) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBeBuildingArmoury, DAT_AICState::ptr)(playerID, mapper)) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBeBuildingHovel, DAT_AICState::ptr)(playerID, mapper)) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBuildMarketArmoryGranaryBarracksMercenarypost, + DAT_AICState::ptr)(playerID, mapper)) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldNotBuildKillingPits, DAT_AICState::ptr)(playerID, mapper)) { + return FALSE; + } + + int const tile = this->aivs[aivID].aivBuildingSteps[step].location.tile.tile; + y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + 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_GATE_STONE1A || mapper == Commands::M_MAPPER_GATE_STONE2A) { + if (this->aivs[aivID].keepOrientation == 0 || this->aivs[aivID].keepOrientation == 4) { + orientation = 81; + } else { + orientation = 80; + } + } else if (mapper == Commands::M_MAPPER_GATE_STONE1B || mapper == Commands::M_MAPPER_GATE_STONE2B) { + if (this->aivs[aivID].keepOrientation == 2 || this->aivs[aivID].keepOrientation == 6) { + orientation = 81; + } else { + orientation = 80; + } + } 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; + } else if (mapper == Commands::M_MAPPER_DRAWBRIDGE) { + DAT_TileMapState::instance.buildingPlacementFail = FALSE; + MACRO_CALL_MEMBER(Map::TileMapState_Func::checkDrawbridgePlacement, DAT_TileMapState::ptr)(x, y); + if (DAT_TileMapState::instance.buildingPlacementFail) { + return FALSE; + } + orientation = DAT_TileMapState::instance.uiBuildingRotation; + } + + if (this->aivs[aivID].aivBuildingSteps[step].buildStatus == AIVBSS_built) { + // rebuild a destroyed building + int const buildingID = (short)DAT_TileMapState::instance.BuildingLayer[tile]; + if ((short)DAT_BuildingsState::instance.buildings[buildingID].buildingType + == MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::convertCommandBuildingTypeToBuildingType, + DAT_BuildingsState::ptr)(mapper)) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker == 0 + && DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker == 0 + && this->aivs[aivID].aivBuildingSteps[step].wait > 0) { + --this->aivs[aivID].aivBuildingSteps[step].wait; + return FALSE; + } + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(mapper, playerID, FALSE)) { + return FALSE; + } + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldWaitWithBuildingAPositiveFearFactor, DAT_AICState::ptr)( + playerID, mapper)) { + return FALSE; + } + if (MACRO_CALL_MEMBER(AICState_Func::shouldWaitWithBuildingNegativeFearFactor, DAT_AICState::ptr)( + playerID, mapper)) { + return FALSE; + } + + if (orientation == 15) { + DAT_TileMapState::instance.DAT_TempBuildingRotation = 0; + } else { + DAT_TileMapState::instance.DAT_TempBuildingRotation = orientation / 2; + } + int const buildingSize = MACRO_CALL_MEMBER( + Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)(mapper); + 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].field242_0x2c8 = 1; + MACRO_CALL_MEMBER(AICState_Func::setupWorkshopProductionType, DAT_AICState::ptr)(playerID, placedID); + } + if (!DAT_TileMapState::instance.buildingPlacementFail) { + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_built; + } else if (mapper == Commands::M_MAPPER_WHEATFARM || mapper == Commands::M_MAPPER_HOPSFARM + || mapper == Commands::M_MAPPER_APPLEFARM || mapper == Commands::M_MAPPER_CATTLEFARM) { + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_insufficient_roomUnk; + } else { + this->aivs[aivID].aivBuildingSteps[step].buildStatus = AIVBSS_insufficient_resources; + } + 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/aiPlaceAdditionalOxtether.cpp b/src/OpenSHC/AI/AIVState/aiPlaceAdditionalOxtether.cpp new file mode 100644 index 000000000..346e3ca0b --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceAdditionalOxtether.cpp @@ -0,0 +1,57 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.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 0x004EFC30 + void AIVState::aiPlaceAdditionalOxtether(PlayerID playerID) + { + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_OXENBASE, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return; + } + + int const quarryID = DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk; + MACRO_CALL_MEMBER(AIVState_Func::findSuitableGridLocationFittingCriteria, this)( + (short)DAT_BuildingsState::instance.buildings[quarryID].x / 5 + + DAT_AIVDefinedData::instance + .OffsetsForFiveByFiveAreaSpotCheck[this->mapExtraInfo.algBuildingPlacementNudge % 9] + .x, + (short)DAT_BuildingsState::instance.buildings[quarryID].y / 5 + + DAT_AIVDefinedData::instance + .OffsetsForFiveByFiveAreaSpotCheck[this->mapExtraInfo.algBuildingPlacementNudge % 9] + .y); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + + ++this->heatMaps[this->buildingApproriateGridXPosition][this->buildingAppropriateGridYPosition].structureCount; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + this->buildingApproriateGridXPosition * 5, this->buildingAppropriateGridYPosition * 5, + Commands::M_MAPPER_OXENBASE, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_OXENBASE), + 15); + + // link the new ox tether to the first free slot of the quarry + int const oxTetherID = DAT_TileMapState::instance.placedBuildingID; + for (int tetherIndex = 0; tetherIndex < 3; ++tetherIndex) { + if (DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[tetherIndex] == 0) { + DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[tetherIndex] = (short)oxTetherID; + break; + } + } + DAT_BuildingsState::instance.buildings[oxTetherID].oxtetherLinkedQuarryID = (short)quarryID; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceBrazier.cpp b/src/OpenSHC/AI/AIVState/aiPlaceBrazier.cpp new file mode 100644 index 000000000..dea520702 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceBrazier.cpp @@ -0,0 +1,49 @@ +#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_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 0x004ED300 + void AIVState::aiPlaceBrazier(PlayerID playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[AIVUT_BRAZIER] <= 0) { + return; + } + for (int i = 0; i < 10; ++i) { + int const tile = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[AIVUT_BRAZIER][i]; + if (tile <= 0) { + continue; + } + int const entityID = DAT_TileMapState::instance.EntityLayer[tile]; + if (entityID != 0 + && DAT_EntityState::instance.entityArray[entityID].entityType == Map::Entities::ET_BRAZIER) { + continue; + } + uint const height + = MACRO_CALL_MEMBER(Map::TileMapState_Func::getTotalHeightAtTile, DAT_TileMapState::ptr)(tile); + if ((int)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, + DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile] * 8, height, 0, 0, 0, + Map::Entities::ET_BRAZIER, 0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceFarm.cpp b/src/OpenSHC/AI/AIVState/aiPlaceFarm.cpp new file mode 100644 index 000000000..1b28f86ba --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceFarm.cpp @@ -0,0 +1,31 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.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 0x004EFA60 + BOOLEnum AIVState::aiPlaceFarm(int playerID, MappersEnum buildingType) + { + // despite its declared type, buildingType holds a BuildingType + if (buildingType <= 0) { + return FALSE; + } + if (!MACRO_CALL_MEMBER( + Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, DAT_GameState::ptr)( + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::convertBuildingTypeToCommandBuildingType, + DAT_BuildingsState::ptr)((Map::Buildings::BuildingType)buildingType), + playerID, FALSE)) { + return FALSE; + } + return MACRO_CALL_MEMBER(AIVState_Func::findSuitableFarmLocationAndPlaceFarm, this)(playerID, buildingType) + != 0; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceFlag.cpp b/src/OpenSHC/AI/AIVState/aiPlaceFlag.cpp new file mode 100644 index 000000000..1dc63dacd --- /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(PlayerID 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/aiPlaceIronMine.cpp b/src/OpenSHC/AI/AIVState/aiPlaceIronMine.cpp new file mode 100644 index 000000000..1e09fbedc --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceIronMine.cpp @@ -0,0 +1,36 @@ +#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 0x004EFE30 + void AIVState::aiPlaceIronMine(int playerID) + { + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_IRON_MINE, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return; + } + MACRO_CALL_MEMBER(AIVState_Func::findAppropriateGridLocationForResourceTypeBuilding, this)( + playerID, AIGCRT_IRON); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + ++this->heatMaps[this->buildingApproriateGridXPosition][this->buildingAppropriateGridYPosition].structureCount; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + this->buildingApproriateGridXPosition * 5, this->buildingAppropriateGridYPosition * 5, + Commands::M_MAPPER_IRON_MINE, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_IRON_MINE), + 15); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlacePitchrig.cpp b/src/OpenSHC/AI/AIVState/aiPlacePitchrig.cpp new file mode 100644 index 000000000..1097dc076 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlacePitchrig.cpp @@ -0,0 +1,36 @@ +#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 0x004EFD90 + void AIVState::aiPlacePitchrig(int playerID) + { + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_PITCH_WORKINGS, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return; + } + MACRO_CALL_MEMBER(AIVState_Func::findAppropriateGridLocationForResourceTypeBuilding, this)( + playerID, AIGCRT_OIL); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + ++this->heatMaps[this->buildingApproriateGridXPosition][this->buildingAppropriateGridYPosition].structureCount; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + this->buildingApproriateGridXPosition * 5, this->buildingAppropriateGridYPosition * 5, + Commands::M_MAPPER_PITCH_WORKINGS, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_PITCH_WORKINGS), + 15); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceQuarryAndOxtether.cpp b/src/OpenSHC/AI/AIVState/aiPlaceQuarryAndOxtether.cpp new file mode 100644 index 000000000..b714825ce --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceQuarryAndOxtether.cpp @@ -0,0 +1,73 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.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 0x004EFED0 + void AIVState::aiPlaceQuarryAndOxtether(int playerID) + { + if (!MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkRequiredResourcesForBuildingOrPlanToBuy, + DAT_GameState::ptr)(Commands::M_MAPPER_QUARRY, playerID, FALSE) + || DAT_BuildingsState::instance.unknownCountdown01 < 20) { + return; + } + MACRO_CALL_MEMBER(AIVState_Func::findAppropriateGridLocationForResourceTypeBuilding, this)( + playerID, AIGCRT_BOULDER); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + + ++this->heatMaps[this->buildingApproriateGridXPosition][this->buildingAppropriateGridYPosition].structureCount; + int const quarryXOffset + = DAT_AIVDefinedData::instance + .OffsetsForClockwiseThreeByThreeAreaCheck[this->mapExtraInfo.algBuildingPlacementNudge % 9] + .x; + int const quarryYOffset + = DAT_AIVDefinedData::instance + .OffsetsForClockwiseThreeByThreeAreaCheck[this->mapExtraInfo.algBuildingPlacementNudge % 9] + .y; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + quarryXOffset + this->buildingApproriateGridXPosition * 5, + quarryYOffset + this->buildingAppropriateGridYPosition * 5, Commands::M_MAPPER_QUARRY, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_QUARRY), + 15); + if (DAT_TileMapState::instance.buildingPlacementFail) { + return; + } + int const quarryID = DAT_TileMapState::instance.placedBuildingID; + MACRO_CALL_MEMBER(AIVState_Func::findSuitableGridLocationFittingCriteria, this)( + this->buildingApproriateGridXPosition, this->buildingAppropriateGridYPosition); + if (this->buildingApproriateGridXPosition == -1) { + return; + } + + // place an ox tether on the first free side and link it to the quarry + for (int side = 0; side < 4; ++side) { + int const xOffset = DAT_AIVDefinedData::instance.OffsetsForSpotCheckThreeByThreeAreaOnTheSide[side].x; + int const yOffset = DAT_AIVDefinedData::instance.OffsetsForSpotCheckThreeByThreeAreaOnTheSide[side].y; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + xOffset + this->buildingApproriateGridXPosition * 5, + yOffset + this->buildingAppropriateGridYPosition * 5, Commands::M_MAPPER_OXENBASE, + MACRO_CALL_MEMBER(Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)( + Commands::M_MAPPER_OXENBASE), + 15); + if (!DAT_TileMapState::instance.buildingPlacementFail) { + int const oxTetherID = DAT_TileMapState::instance.placedBuildingID; + DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[0] = (short)oxTetherID; + DAT_BuildingsState::instance.buildings[oxTetherID].oxtetherLinkedQuarryID = (short)quarryID; + return; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp b/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp new file mode 100644 index 000000000..b115a429c --- /dev/null +++ b/src/OpenSHC/AI/AIVState/aiPlaceWoodcuttershut.cpp @@ -0,0 +1,43 @@ +#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[this->buildingApproriateGridXPosition][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[this->buildingApproriateGridXPosition][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..b4e1b2a48 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/applyAIV.cpp @@ -0,0 +1,186 @@ +#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; + int index = 0; + for (y = 0; 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; + if (!MACRO_CALL_MEMBER(Rendering::ViewportRenderState_Func::xyAreValid, + DAT_ViewportRenderState::ptr)(this->aivs[aivID].keepXOffset + x, realY)) { + continue; + } + 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 + = DAT_ViewportRenderState::instance.translationMatrix[realY].addXgetTile + + this->aivs[aivID].keepXOffset + x; + 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; + if (startY >= 100) { + break; + } + 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; + } + uint const realX = this->aivs[aivID].keepXOffset + x; + uint const realY = this->aivs[aivID].keepYOffset + y; + if (realX <= 399 && realY <= 399 + && DAT_ViewportRenderState::instance.DAT_BinaryTileMap400x400[realY * 400 + realX]) { + 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] + = DAT_ViewportRenderState::instance.translationMatrix[realY].addXgetTile + + this->aivs[aivID].keepXOffset + x; + ++this->aivs[aivID].wallLocationsArrayIndex; + if (step > this->aivs[aivID].totalSteps) { + this->aivs[aivID].totalSteps = step; + } + this->isHandled[index] = 1; + } else { + 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) { + y = 99 - relativeX; + x = relativeY; + } + int const tile + = DAT_ViewportRenderState::instance.translationMatrix[this->aivs[aivID].keepYOffset + y].addXgetTile + + this->aivs[aivID].keepXOffset + x; + DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[unitType][i] = tile; + if (tile > 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/clearTheHeatmaps.cpp b/src/OpenSHC/AI/AIVState/clearTheHeatmaps.cpp new file mode 100644 index 000000000..32dd8cff3 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/clearTheHeatmaps.cpp @@ -0,0 +1,18 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EDC90 + void AIVState::clearTheHeatmaps() + { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + sizeof(this->heatMaps), 0, this->heatMaps); + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/computeAIVPlacementFit.cpp b/src/OpenSHC/AI/AIVState/computeAIVPlacementFit.cpp new file mode 100644 index 000000000..54dbba337 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/computeAIVPlacementFit.cpp @@ -0,0 +1,68 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // returns the lowest AIV step (minus one) that can not be placed, 999999 is a perfect fit + // FUNCTION: STRONGHOLDCRUSADER 0x004EF8C0 + int AIVState::computeAIVPlacementFit(int playerID) + { + int minSteps = 1000000; + DAT_TileMapState::instance.buildingPlacementProperty_7 = 0; + DAT_TileMapState::instance.buildingPlacementProperty_6 = 0; + DAT_TileMapState::instance.buildingPlacementProperty_5 = 0; + DAT_TileMapState::instance.buildingPlacementProperty_3 = 0; + for (int y = 0, index = 0; y < 100; ++y) { + for (int x = 0; x < 100; ++x, ++index) { + int const type = this->constructions[index]; + if (type == 0) { + continue; + } + if (type == 1) { + this->isHandled[index] = 1; + continue; + } + MappersEnum const mapper = MACRO_CALL_MEMBER( + AIVState_Func::convertAIVBuildingTypeToCommandBuildingType, this)((AIV::AIVBuildingType2)type); + DAT_TileMapState::instance.buildingHeightLimit + = DAT_BuildingDefinedData::instance.BuildingPlacement_HeightLimit[MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::convertCommandBuildingTypeToBuildingType, + DAT_BuildingsState::ptr)(mapper)]; + DAT_TileMapState::instance.buildingMaxHeightDifference + = DAT_BuildingDefinedData::instance.BuildingPlacement_MaxHeightDifference[MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::convertCommandBuildingTypeToBuildingType, + DAT_BuildingsState::ptr)(mapper)]; + ++this->buildingCounter; + uint const realX = this->aivs[playerID].keepXOffset + x; + uint const realY = this->aivs[playerID].keepYOffset + y; + if (realX > 399 || realY > 399 + || !DAT_ViewportRenderState::instance.DAT_BinaryTileMap400x400[realY * 400 + realX]) { + continue; + } + int const tile = DAT_ViewportRenderState::instance.translationMatrix[realY].addXgetTile + realX; + MACRO_CALL_MEMBER(Map::TileMapState_Func::storeMinAndMaxHeightOfArea, DAT_TileMapState::ptr)( + realX, realY, 1); + if (MACRO_CALL_MEMBER(Map::TileMapState_Func::isBuildingPlacementAllowedAtTile, DAT_TileMapState::ptr)( + tile, 0, mapper, 0)) { + this->isHandled[index] = 1; + // steps is accessed as a flat 100 * 100 array + if (this->steps[0][index] < minSteps) { + minSteps = this->steps[0][index]; + } + ++this->invalidTileCounter; + } + } + } + return minSteps - 1; + } + +} +} 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/convertAIVBuildingTypeToCommandBuildingType.cpp b/src/OpenSHC/AI/AIVState/convertAIVBuildingTypeToCommandBuildingType.cpp new file mode 100644 index 000000000..5daa3b42d --- /dev/null +++ b/src/OpenSHC/AI/AIVState/convertAIVBuildingTypeToCommandBuildingType.cpp @@ -0,0 +1,48 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECFE0 + MappersEnum AIVState::convertAIVBuildingTypeToCommandBuildingType(AIVBuildingType2 aivBuildingType) + { + switch (aivBuildingType) { + case AIV::AIVBT_HIGH_WALL: + return Commands::M_MAPPER_WALL; + case AIV::AIVBT_LOW_WALL: + return Commands::M_MAPPER_WOODWALL; + case AIV::AIVBT_CRENEL: + return Commands::M_MAPPER_CRENAL; + case AIV::AIVBT_LOW_CRENEL: + return Commands::M_MAPPER_CRENAL2; + case AIV::AIVBT_STAIRS_1: + return Commands::M_MAPPER_STAIR1; + case AIV::AIVBT_STAIRS_2: + return Commands::M_MAPPER_STAIR2; + case AIV::AIVBT_STAIRS_3: + return Commands::M_MAPPER_STAIR3; + case AIV::AIVBT_STAIRS_4: + return Commands::M_MAPPER_STAIR4; + case AIV::AIVBT_STAIRS_5: + return Commands::M_MAPPER_STAIR5; + case AIV::AIVBT_STAIRS_6Unk: + return Commands::M_MAPPER_STAIR6; + case AIV::AIVBT_MOAT_A: + case AIV::AIVBT_MOAT_B: + case AIV::AIVBT_MOAT_C: + case AIV::AIVBT_MOAT_D: + return Commands::M_MAPPER_MOAT; + case AIV::AIVBT_PITCHDITCH_2: + return Commands::M_MAPPER_PITCH_DITCH; + } + // all other buildings are looked up in a table starting at the first tower + if (aivBuildingType < AIV::AIVBT_TOWER1 || aivBuildingType > AIV::AIVBT_TOWER1 + 79) { + return Commands::M_MAPPER_GENERIC; + } + return DAT_AIVDefinedData::instance.field21_0xf4[aivBuildingType - AIV::AIVBT_TOWER1]; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp b/src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp new file mode 100644 index 000000000..cb4e3aa55 --- /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(PlayerID 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].field242_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..21e6ae516 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp @@ -0,0 +1,91 @@ +#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; + + if (this->algAIndex == this->algBIndex) { + return; + } + uint candidateX; + uint candidateY; + for (;;) { + 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) { + candidateX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + 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) { + break; + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + if (this->algAIndex == this->algBIndex) { + return; + } + } + // the result is the last visited neighbour + this->buildingAppropriateGridYPosition = candidateY; + this->buildingApproriateGridXPosition = candidateX; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForResourceTypeBuilding.cpp b/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForResourceTypeBuilding.cpp new file mode 100644 index 000000000..238f13827 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findAppropriateGridLocationForResourceTypeBuilding.cpp @@ -0,0 +1,148 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // breadth first search from the campground for a grid cell with enough of the given resource + // FUNCTION: STRONGHOLDCRUSADER 0x004EE430 + void AIVState::findAppropriateGridLocationForResourceTypeBuilding( + int playerID, AIGridComputationResourceType resourceType) + { + this->visitCount = 1; + this->algAIndex = 0; + this->algBIndex = 1; + this->buildingApproriateGridXPosition = -1; + this->buildingAppropriateGridYPosition = -1; + if (resourceType == AIGCRT_OIL) { + if (DAT_GameState::instance.playerDataArray[playerID].countPitchRigs + >= DAT_GameState::instance.playerDataArray[playerID].availableOilGridTiles) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].algoOilCooldown > 0) { + --DAT_GameState::instance.playerDataArray[playerID].algoOilCooldown; + return; + } + } else if (resourceType == AIGCRT_IRON) { + if (DAT_GameState::instance.playerDataArray[playerID].countIronMines + >= DAT_GameState::instance.playerDataArray[playerID].availableIronGridTiles) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].algoIronCooldown > 0) { + --DAT_GameState::instance.playerDataArray[playerID].algoIronCooldown; + return; + } + } else if (resourceType == AIGCRT_BOULDER) { + if (DAT_GameState::instance.playerDataArray[playerID].countStoneQuarries + >= DAT_GameState::instance.playerDataArray[playerID].availableBoulderGridTiles) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].algoBoulderCooldown > 0) { + --DAT_GameState::instance.playerDataArray[playerID].algoBoulderCooldown; + return; + } + } + + int const campX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5; + this->heatMapXArray[0] = campX; + int const campY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5; + ++this->mapExtraInfo.algorithmIterationNumber; + this->heatMapYArray[0] = campY; + this->heatMaps[campX][campY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[campX][campY].algorithmVisitCountUnk = this->visitCount; + + while (this->algAIndex != this->algBIndex) { + int const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + this->visitCount = (char)this->heatMaps[x][y].algorithmVisitCountUnk; + if (this->visitCount > 40) { + break; + } + // only the four cardinal directions + for (int direction = 0; direction < 8; direction += 2) { + uint const gridX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + uint const gridY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.yOffset + y; + if (this->heatMaps[x][y].isNotNearEdgeUnk && (gridX > 79 || gridY > 79)) { + continue; + } + if (this->heatMaps[gridX][gridY].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + this->heatMaps[gridX][gridY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + int const blockedCount = (char)this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount + - (char)this->heatMaps[gridX][gridY].impassableCount; + if (blockedCount >= 16) { + continue; + } + int const owner = (char)this->heatMaps[gridX][gridY].tileOwnership; + if ((blockedCount == 0 || resourceType == AIGCRT_IRON) && blockedCount <= 4 + && !this->heatMaps[gridX][gridY].structureCount + && !this->heatMaps[gridX][gridY].destructionBasedPlacementCooldown + && (!this->heatMaps[gridX][gridY].tileOwnership + || DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[owner])) { + if (resourceType == AIGCRT_OIL) { + if ((char)this->heatMaps[gridX][gridY].oilCount > 2 + && (char)this->heatMaps[gridX][gridY].marshCount >= 10 + && this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + this->buildingApproriateGridXPosition = gridX; + this->buildingAppropriateGridYPosition = gridY; + return; + } + } else if (resourceType == AIGCRT_IRON) { + if ((char)this->heatMaps[gridX][gridY].ironCount > 6 + && this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + this->buildingApproriateGridXPosition = gridX; + this->buildingAppropriateGridYPosition = gridY; + return; + } + } else if (resourceType == AIGCRT_BOULDER) { + if ((char)this->heatMaps[gridX][gridY].bouldersCount >= 8 + && this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 40) { + this->buildingApproriateGridXPosition = gridX; + this->buildingAppropriateGridYPosition = gridY; + return; + } + } + } + this->heatMaps[gridX][gridY].algorithmVisitCountUnk = this->visitCount + 1; + this->heatMapXArray[this->algBIndex] = gridX; + this->heatMapYArray[this->algBIndex] = gridY; + ++this->algBIndex; + if ((int)this->algBIndex >= 6400) { + this->algBIndex = 0; + } + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + } + + // nothing found, wait before searching again + if (resourceType == AIGCRT_OIL) { + DAT_GameState::instance.playerDataArray[playerID].algoOilCooldown = 5; + return; + } + if (resourceType == AIGCRT_IRON) { + DAT_GameState::instance.playerDataArray[playerID].algoIronCooldown = 5; + return; + } + if (resourceType == AIGCRT_BOULDER) { + DAT_GameState::instance.playerDataArray[playerID].algoBoulderCooldown = 5; + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findAppropriateWoodCutterGridLocation.cpp b/src/OpenSHC/AI/AIVState/findAppropriateWoodCutterGridLocation.cpp new file mode 100644 index 000000000..37db4676a --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findAppropriateWoodCutterGridLocation.cpp @@ -0,0 +1,102 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // breadth first search from the campground for the grid cell with the best tree score + // FUNCTION: STRONGHOLDCRUSADER 0x004EE140 + void AIVState::findAppropriateWoodCutterGridLocation(int playerID) + { + int bestScore = -100; + uint bestX = -1; + uint bestY = -1; + this->algAIndex = 0; + int foundCount = 0; + this->visitCount = 1; + this->algBIndex = 1; + int const campX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5; + this->heatMapXArray[0] = campX; + int const campY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5; + this->heatMapYArray[0] = campY; + this->buildingApproriateGridXPosition = -1; + this->buildingAppropriateGridYPosition = -1; + if (DAT_GameState::instance.playerDataArray[playerID].algoTreeCooldownUnk > 0) { + --DAT_GameState::instance.playerDataArray[playerID].algoTreeCooldownUnk; + return; + } + ++this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[campX][campY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[campX][campY].algorithmVisitCountUnk = this->visitCount; + + while (this->algAIndex != this->algBIndex) { + int const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + this->visitCount = (char)this->heatMaps[x][y].algorithmVisitCountUnk; + if (this->visitCount > 40) { + break; + } + // only the four cardinal directions + for (int direction = 0; direction < 8; direction += 2) { + uint const gridX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + uint const gridY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.yOffset + y; + if (this->heatMaps[x][y].isNotNearEdgeUnk && (gridX > 79 || gridY > 79)) { + continue; + } + if (this->heatMaps[gridX][gridY].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + this->heatMaps[gridX][gridY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + char const notInLargestArea = (char)this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount; + if (notInLargestArea >= 16 || this->heatMaps[gridX][gridY].destructionBasedPlacementCooldown) { + continue; + } + if (notInLargestArea < 6 && (char)this->heatMaps[gridX][gridY].treeCount > 0) { + ++foundCount; + int score = (char)this->heatMaps[gridX][gridY].treeCount * 5 - this->visitCount * 3; + // prefer cells that were not used for a woodcutter recently + if (this->heatMaps[gridX][gridY].woodRelatedCountdownTo0) { + if (score > 0) { + score /= 2; + } else { + score *= 2; + } + } + if (score > bestScore) { + bestX = gridX; + bestY = gridY; + bestScore = score; + } + if (foundCount > 10 || (foundCount > 5 && this->visitCount > 20) + || (foundCount > 0 && this->visitCount > 30)) { + break; + } + } + this->heatMaps[gridX][gridY].algorithmVisitCountUnk = this->visitCount + 1; + this->heatMapXArray[this->algBIndex] = gridX; + this->heatMapYArray[this->algBIndex] = gridY; + ++this->algBIndex; + if ((int)this->algBIndex >= 6400) { + this->algBIndex = 0; + } + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + } + if (foundCount > 0) { + this->buildingApproriateGridXPosition = bestX; + this->buildingAppropriateGridYPosition = bestY; + return; + } + DAT_GameState::instance.playerDataArray[playerID].algoTreeCooldownUnk = 5; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findSpotNearEngineersGuild.cpp b/src/OpenSHC/AI/AIVState/findSpotNearEngineersGuild.cpp new file mode 100644 index 000000000..e59f92932 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findSpotNearEngineersGuild.cpp @@ -0,0 +1,30 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EFB90 + BOOLEnum AIVState::findSpotNearEngineersGuild(int playerID) + { + int const guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) { + return FALSE; + } + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) { + return FALSE; + } + MACRO_CALL_MEMBER(AIVState_Func::findSuitableGridLocationFittingCriteria, this)( + (short)DAT_BuildingsState::instance.buildings[guildID].x / 5, + (short)DAT_BuildingsState::instance.buildings[guildID].y / 5); + if (this->buildingApproriateGridXPosition == -1) { + return FALSE; + } + ++this->heatMaps[this->buildingApproriateGridXPosition][this->buildingAppropriateGridYPosition].structureCount; + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findSuitableFarmLocationAndPlaceFarm.cpp b/src/OpenSHC/AI/AIVState/findSuitableFarmLocationAndPlaceFarm.cpp new file mode 100644 index 000000000..0c5dcf02b --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findSuitableFarmLocationAndPlaceFarm.cpp @@ -0,0 +1,117 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.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" + +namespace OpenSHC { +namespace AI { + + // breadth first search from the campground for an oasis grid cell and places the farm there + // FUNCTION: STRONGHOLDCRUSADER 0x004EDDF0 + int AIVState::findSuitableFarmLocationAndPlaceFarm(PlayerID playerID, MappersEnum farmType) + { + if (DAT_BuildingsState::instance.unknownCountdown01 < 20 + || DAT_GameState::instance.playerDataArray[playerID].countFarms + >= DAT_GameState::instance.playerDataArray[playerID].availableOasisGridTiles) { + return 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].algoOasisCooldownUnk > 0) { + --DAT_GameState::instance.playerDataArray[playerID].algoOasisCooldownUnk; + return 0; + } + + this->visitCount = 1; + this->algAIndex = 0; + this->algBIndex = 1; + // despite its declared type, farmType holds the BuildingType of the farm + // the original leaves mapper untouched when farmType is none of the four below + MappersEnum mapper; + if (farmType == Map::Buildings::BT_WHEATFARM) { + mapper = Commands::M_MAPPER_WHEATFARM; + } else if (farmType == Map::Buildings::BT_HOPFARM) { + mapper = Commands::M_MAPPER_HOPSFARM; + } else if (farmType == Map::Buildings::BT_APPLEFARM) { + mapper = Commands::M_MAPPER_APPLEFARM; + } else if (farmType == Map::Buildings::BT_DAIRYFARM) { + mapper = Commands::M_MAPPER_CATTLEFARM; + } + int const campX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5; + this->heatMapXArray[0] = campX; + int const campY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5; + ++this->mapExtraInfo.algorithmIterationNumber; + this->heatMapYArray[0] = campY; + this->heatMaps[campX][campY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[campX][campY].algorithmVisitCountUnk = this->visitCount; + + while (this->algAIndex != this->algBIndex) { + int const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + this->visitCount = (char)this->heatMaps[x][y].algorithmVisitCountUnk; + if (this->visitCount > 40) { + break; + } + // only the four cardinal directions + for (int direction = 0; direction < 8; direction += 2) { + uint const gridX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + uint const gridY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.yOffset + y; + if (this->heatMaps[x][y].isNotNearEdgeUnk && (gridX > 79 || gridY > 79)) { + continue; + } + if (this->heatMaps[gridX][gridY].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + int const notInLargestArea = (char)this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount; + this->heatMaps[gridX][gridY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + if (notInLargestArea > 16) { + continue; + } + this->heatMaps[gridX][gridY].algorithmVisitCountUnk = this->visitCount + 1; + this->heatMapXArray[this->algBIndex] = gridX; + this->heatMapYArray[this->algBIndex] = gridY; + ++this->algBIndex; + if ((int)this->algBIndex >= 6400) { + this->algBIndex = 0; + } + if (this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount + || this->heatMaps[gridX][gridY].structureCount || this->heatMaps[gridX][gridY].treeCount + || this->heatMaps[gridX][gridY].destructionBasedPlacementCooldown + || (char)this->heatMaps[gridX][gridY].oasisScrubCount < 25 + || (char)this->heatMaps[gridX][gridY].oasisThickScrubCount < 14) { + continue; + } + int const xOffset + = DAT_AIVDefinedData::instance.TilesSelfAndRightThree[this->mapExtraInfo.algBuildingPlacementNudge] + .x; + int const yOffset + = DAT_AIVDefinedData::instance.TilesSelfAndRightThree[this->mapExtraInfo.algBuildingPlacementNudge] + .y; + ++this->counter; + MACRO_CALL_MEMBER(Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + xOffset + gridX * 5, yOffset + gridY * 5, mapper, + MACRO_CALL_MEMBER( + Map::TileMapState_Func::getBuildingSizeForCommandBuildingType, DAT_TileMapState::ptr)(mapper), + 15); + if (!DAT_TileMapState::instance.buildingPlacementFail) { + this->heatMaps[gridX][gridY].structureCount = 1; + return 1; + } + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + } + DAT_GameState::instance.playerDataArray[playerID].algoOasisCooldownUnk = 5; + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/findSuitableGridLocationFittingCriteria.cpp b/src/OpenSHC/AI/AIVState/findSuitableGridLocationFittingCriteria.cpp new file mode 100644 index 000000000..7797d5b61 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/findSuitableGridLocationFittingCriteria.cpp @@ -0,0 +1,75 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // breadth first search for the closest free grid cell + // FUNCTION: STRONGHOLDCRUSADER 0x004EEBD0 + void AIVState::findSuitableGridLocationFittingCriteria(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 const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + 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].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + this->heatMaps[candidateX][candidateY].algorithmIterationNumber + = this->mapExtraInfo.algorithmIterationNumber; + if (notInLargestArea >= 15) { + continue; + } + 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) { + this->buildingApproriateGridXPosition = candidateX; + this->buildingAppropriateGridYPosition = candidateY; + return; + } + 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; + } + } + ++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..b8deb1d27 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/hostChecksLobbyAIVAvailability.cpp @@ -0,0 +1,123 @@ +#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) { + 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 (this->aivFileAvailabilityPerAIArray[aiIndex] == 0) { + available = FALSE; + break; + } + } else if (player == localPlayer) { + if (this->aivFileAvailabilityPerAIArray[aiIndex] == 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; + } + BOOLEnum hasAIV = FALSE; + for (int i = 0; i < DAT_GameCore::instance.numOfAIsWithCastleUnk; ++i) { + if (DAT_GameSynchronyState::instance.currentAIArray[player] + == 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/incrementStructureHeatMapTile.cpp b/src/OpenSHC/AI/AIVState/incrementStructureHeatMapTile.cpp new file mode 100644 index 000000000..ab597b462 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/incrementStructureHeatMapTile.cpp @@ -0,0 +1,19 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // records a structure in the 5x5 tile heat map cell of the given tile coordinates + // FUNCTION: STRONGHOLDCRUSADER 0x004ED3B0 + void AIVState::incrementStructureHeatMapTile(int x, int y) + { + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) { + return; + } + ++this->heatMaps[x / 5][y / 5].structureCount; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/recomputeAIAvailableGridTiles.cpp b/src/OpenSHC/AI/AIVState/recomputeAIAvailableGridTiles.cpp new file mode 100644 index 000000000..6810e985f --- /dev/null +++ b/src/OpenSHC/AI/AIVState/recomputeAIAvailableGridTiles.cpp @@ -0,0 +1,126 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TerrainDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // counts the grid cells around the campground that are usable for the resource buildings + // FUNCTION: STRONGHOLDCRUSADER 0x004EE830 + void AIVState::recomputeAIAvailableGridTiles(int playerID) + { + DAT_GameState::instance.playerDataArray[playerID].algoTreeCooldownUnk = -1; + DAT_GameState::instance.playerDataArray[playerID].algoOasisCooldownUnk = -1; + DAT_GameState::instance.playerDataArray[playerID].algoBoulderCooldown = -1; + DAT_GameState::instance.playerDataArray[playerID].algoIronCooldown = -1; + DAT_GameState::instance.playerDataArray[playerID].algoOilCooldown = -1; + DAT_GameState::instance.playerDataArray[playerID].availableTreeGridTiles = 0; + DAT_GameState::instance.playerDataArray[playerID].availableOasisGridTiles = 0; + DAT_GameState::instance.playerDataArray[playerID].availableBoulderGridTiles = 0; + DAT_GameState::instance.playerDataArray[playerID].availableIronGridTiles = 0; + DAT_GameState::instance.playerDataArray[playerID].availableOilGridTiles = 0; + this->visitCount = 1; + this->algBIndex = 1; + this->algAIndex = 0; + int const campX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry / 5; + this->heatMapXArray[0] = campX; + int const campY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry / 5; + ++this->mapExtraInfo.algorithmIterationNumber; + this->heatMapYArray[0] = campY; + this->heatMaps[campX][campY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + this->heatMaps[campX][campY].algorithmVisitCountUnk = this->visitCount; + + while (this->algAIndex != this->algBIndex) { + int const x = this->heatMapXArray[this->algAIndex]; + int const y = this->heatMapYArray[this->algAIndex]; + this->visitCount = (char)this->heatMaps[x][y].algorithmVisitCountUnk; + if (this->visitCount > 40) { + break; + } + // only the four cardinal directions + for (int direction = 0; direction < 8; direction += 2) { + uint const gridX + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.xOffset + x; + uint const gridY + = DAT_TerrainDefinedData::instance.clockwiseCardinalTranslationMatrix[direction].int_.yOffset + y; + if (this->heatMaps[x][y].isNotNearEdgeUnk && (gridX > 79 || gridY > 79)) { + continue; + } + if (this->heatMaps[gridX][gridY].algorithmIterationNumber + == this->mapExtraInfo.algorithmIterationNumber) { + continue; + } + int const impassable = (char)this->heatMaps[gridX][gridY].impassableCount; + this->heatMaps[gridX][gridY].algorithmIterationNumber = this->mapExtraInfo.algorithmIterationNumber; + int const blockedCount + = (char)this->heatMaps[gridX][gridY].tilesNotPartOfLargestAreaCount - impassable; + if (blockedCount >= 16) { + continue; + } + if ((char)this->heatMaps[gridX][gridY].oilCount > 2 + && (char)this->heatMaps[gridX][gridY].marshCount >= 10 && blockedCount == 0) { + if (this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + ++DAT_GameState::instance.playerDataArray[playerID].availableOilGridTiles; + } + } else if ((char)this->heatMaps[gridX][gridY].ironCount > 6 && blockedCount <= 4) { + if (this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + ++DAT_GameState::instance.playerDataArray[playerID].availableIronGridTiles; + } + } else if ((char)this->heatMaps[gridX][gridY].bouldersCount >= 8 && blockedCount == 0) { + if (this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 40) { + ++DAT_GameState::instance.playerDataArray[playerID].availableBoulderGridTiles; + } + } else if ((char)this->heatMaps[gridX][gridY].treeCount > 0 && blockedCount < 6) { + if (this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + ++DAT_GameState::instance.playerDataArray[playerID].availableTreeGridTiles; + } + } else if ((char)this->heatMaps[gridX][gridY].oasisScrubCount >= 25 + && (char)this->heatMaps[gridX][gridY].oasisThickScrubCount >= 14 && blockedCount == 0 + && this->heatMaps[gridX][gridY].highestTerrainHeight + - this->heatMaps[gridX][gridY].lowestTerrainHeight + < 12) { + ++DAT_GameState::instance.playerDataArray[playerID].availableOasisGridTiles; + } + this->heatMaps[gridX][gridY].algorithmVisitCountUnk = this->visitCount + 1; + this->heatMapXArray[this->algBIndex] = gridX; + this->heatMapYArray[this->algBIndex] = gridY; + ++this->algBIndex; + if ((int)this->algBIndex >= 6400) { + this->algBIndex = 0; + } + } + ++this->algAIndex; + if ((int)this->algAIndex >= 6400) { + this->algAIndex = 0; + } + } + + // allow searching right away if there is anything to find + if (DAT_GameState::instance.playerDataArray[playerID].availableTreeGridTiles > 0) { + DAT_GameState::instance.playerDataArray[playerID].algoTreeCooldownUnk = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].availableOasisGridTiles > 0) { + DAT_GameState::instance.playerDataArray[playerID].algoOasisCooldownUnk = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].availableBoulderGridTiles > 0) { + DAT_GameState::instance.playerDataArray[playerID].algoBoulderCooldown = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].availableIronGridTiles > 0) { + DAT_GameState::instance.playerDataArray[playerID].algoIronCooldown = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].availableOilGridTiles > 0) { + DAT_GameState::instance.playerDataArray[playerID].algoOilCooldown = 0; + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp b/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp new file mode 100644 index 000000000..930ebd328 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/recomputeHeatmaps.cpp @@ -0,0 +1,186 @@ +#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 gridY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile] / 5; + int const gridX = (tile + - DAT_ViewportRenderState::instance + .translationMatrix[DAT_ViewportRenderState::instance + .tileTranslationMatrix_YComponent[tile]] + .addXgetTile) + / 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; + ++this->heatMaps[gridX][y / 5].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/resetCountdownInHeatMap.cpp b/src/OpenSHC/AI/AIVState/resetCountdownInHeatMap.cpp new file mode 100644 index 000000000..69f26e8a1 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/resetCountdownInHeatMap.cpp @@ -0,0 +1,14 @@ +#include "../AIVState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // blocks building on the 5x5 heat map cell of the given tile for a while, e.g. after a building got destroyed + // FUNCTION: STRONGHOLDCRUSADER 0x004EDCB0 + void AIVState::resetCountdownInHeatMap(int x, int y) + { + this->heatMaps[x / 5][y / 5].destructionBasedPlacementCooldown = 10; + } + +} +} 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/selectBestAIVwithRandomStart.cpp b/src/OpenSHC/AI/AIVState/selectBestAIVwithRandomStart.cpp new file mode 100644 index 000000000..486724816 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/selectBestAIVwithRandomStart.cpp @@ -0,0 +1,62 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/FilePackager.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/FilePackagerObj.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // starts at a random castle and picks the first one that fits perfectly, otherwise the best fitting one + // FUNCTION: STRONGHOLDCRUSADER 0x004F13F0 + void AIVState::selectBestAIVwithRandomStart(int aivID) + { + int bestFit = 0; + int bestCastleID = -1; + uint castleID = (byte)SEC_RNG::instance.currentNumber2 & 7; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + sizeof(this->isHandled), 0, this->isHandled); + for (int tries = 0; tries < 8; ++tries) { + ++castleID; + if ((int)castleID >= 8) { + castleID = 0; + } + int const aiType = this->aivs[aivID].aiType; + if (!this->aivFilePresent[aiType - 2][castleID]) { + continue; + } + if (!MACRO_CALL_MEMBER(IO::FilePackager_Func::unpackAIVFile, FilePackagerObj::ptr)( + DAT_AIVDefinedData::instance.AIVSectionAddressArray, + DAT_AIVDefinedData::instance.AIVFileNames[aiType - 2][castleID])) { + continue; + } + MACRO_CALL_MEMBER(AIVState_Func::rotateAIV, this)(this->aivs[aivID].keepOrientation); + int const fit = MACRO_CALL_MEMBER(AIVState_Func::computeAIVPlacementFit, this)(aivID); + if (fit <= 0) { + continue; + } + if (fit == 999999) { + // the first perfectly fitting castle is taken + this->aivs[aivID].castleID = castleID; + this->aivs[aivID].aivSubType = 2; + return; + } + if (fit > bestFit) { + bestFit = fit; + bestCastleID = castleID; + } + } + if (bestCastleID >= 0) { + this->aivs[aivID].aivSubType = 1; + this->aivs[aivID].castleID = bestCastleID; + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/setAIVFilePresenceByFileHashArray.cpp b/src/OpenSHC/AI/AIVState/setAIVFilePresenceByFileHashArray.cpp new file mode 100644 index 000000000..1d9b6899a --- /dev/null +++ b/src/OpenSHC/AI/AIVState/setAIVFilePresenceByFileHashArray.cpp @@ -0,0 +1,47 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // an AIV is only usable in multiplayer if all players have the same file + // FUNCTION: STRONGHOLDCRUSADER 0x004ECDB0 + void AIVState::setAIVFilePresenceByFileHashArray() + { + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SKIRMISH_SINGLE_PLAYER) { + for (int aiIndex = 0; aiIndex < 16; ++aiIndex) { + for (int castleID = 0; castleID < 8; ++castleID) { + this->aivFilePresent[aiIndex][castleID] = 1; + } + } + return; + } + for (int aiIndex = 0; aiIndex < 16; ++aiIndex) { + for (int castleID = 0; castleID < 8; ++castleID) { + int hash = -1; + this->aivFilePresent[aiIndex][castleID] = 1; + for (int player = 1; player < 9; ++player) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1) { + continue; + } + if (hash == -1) { + hash = this->receivedAIVFileHash[player][aiIndex][castleID]; + if (hash == -1) { + this->aivFilePresent[aiIndex][castleID] = 0; + break; + } + } else if (hash != this->receivedAIVFileHash[player][aiIndex][castleID]) { + this->aivFilePresent[aiIndex][castleID] = 0; + break; + } + } + if (hash == -1) { + this->aivFilePresent[aiIndex][castleID] = 0; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/setAvailableAIV.cpp b/src/OpenSHC/AI/AIVState/setAvailableAIV.cpp new file mode 100644 index 000000000..8efa8cb7b --- /dev/null +++ b/src/OpenSHC/AI/AIVState/setAvailableAIV.cpp @@ -0,0 +1,40 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/ResourceManager.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_ResourceManager.hpp" + +namespace OpenSHC { +namespace AI { + + // hashes all AIV files and lists the AIs that have at least one AIV + // FUNCTION: STRONGHOLDCRUSADER 0x004ECAD0 + void AIVState::setAvailableAIV() + { + for (int i = 0; i < 20; ++i) { + this->aivFileAvailabilityPerAIArray[i] = 0; + } + this->numberOfAIsWithAIV = 0; + for (int aiIndex = 0; aiIndex < 16; ++aiIndex) { + int availableAIVs = 0; + for (int castleID = 0; castleID < 8; ++castleID) { + int const hash = MACRO_CALL_MEMBER(IO::ResourceManager_Func::getSimpleFirst1024ByteSumOfFile, + DAT_ResourceManager::ptr)(DAT_AIVDefinedData::instance.AIVFileNames[aiIndex][castleID]); + this->aivFileHash[aiIndex][castleID] = hash; + if (hash != -1) { + ++availableAIVs; + } + } + if (availableAIVs != 0) { + this->aivFileAvailabilityPerAIArray[aiIndex] = availableAIVs; + DAT_GameCore::instance.arrayOfLordIdsWithAIVsUnk[this->numberOfAIsWithAIV] = aiIndex + 1; + ++this->numberOfAIsWithAIV; + } + } + DAT_GameCore::instance.numOfAIsWithCastleUnk = this->numberOfAIsWithAIV; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/setKeepOffsetAndOrientation.cpp b/src/OpenSHC/AI/AIVState/setKeepOffsetAndOrientation.cpp new file mode 100644 index 000000000..06041b5a3 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/setKeepOffsetAndOrientation.cpp @@ -0,0 +1,32 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECF70 + void AIVState::setKeepOffsetAndOrientation(int aivID, int keepX, int keepY) + { + // the AIV is 100x100 tiles with the keep at 43/43 + this->aivs[aivID].keepX = keepX; + this->aivs[aivID].keepY = keepY; + this->aivs[aivID].keepXOffset = keepX - 43; + this->aivs[aivID].keepYOffset = keepY - 43; + // orient the castle towards the map center + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::calculatePreferredRelativeOrientation, + DAT_DirectionAlgorithmState::ptr)(keepX, keepY, 200, 200); + this->aivs[aivID].keepOrientation = DAT_DirectionAlgorithmState::instance.orientation & 0xfffe; + if (this->aivs[aivID].keepOrientation == 6) { + this->aivs[aivID].keepOrientation = 2; + return; + } + if (this->aivs[aivID].keepOrientation == 2) { + this->aivs[aivID].keepOrientation = 6; + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/syncAIPlayerNamesAndBuildIntervals.cpp b/src/OpenSHC/AI/AIVState/syncAIPlayerNamesAndBuildIntervals.cpp new file mode 100644 index 000000000..d5eb20c5d --- /dev/null +++ b/src/OpenSHC/AI/AIVState/syncAIPlayerNamesAndBuildIntervals.cpp @@ -0,0 +1,35 @@ +// disable deprecation warnings for strcpy +#pragma warning(disable : 4996) + +#include "../AIVState.func.hpp" + +#include "OpenSHC/AI/AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_AICState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // restores the names of the AI players from the skirmish results and refreshes the AIC build intervals + // FUNCTION: STRONGHOLDCRUSADER 0x004ECE60 + void AIVState::syncAIPlayerNamesAndBuildIntervals() + { + for (int playerID = 1; playerID < 9; ++playerID) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] != -1 + || DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0) { + continue; + } + strcpy(DAT_GameSynchronyState::instance.DAT_PlayerNames[playerID], + DAT_GameSynchronyState::instance.finalResults.names[playerID]); + int const aivID = DAT_GameState::instance.playerDataArray[playerID].aivID; + if (aivID != 0) { + this->aivs[aivID].aivPoorLimit_OR_AIC_buildInterval + = MACRO_CALL_MEMBER(AICState_Func::getAIBuildInterval, DAT_AICState::ptr)(playerID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/tryPlaceAIVAndReturnFitPercentage.cpp b/src/OpenSHC/AI/AIVState/tryPlaceAIVAndReturnFitPercentage.cpp new file mode 100644 index 000000000..68b4a4304 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/tryPlaceAIVAndReturnFitPercentage.cpp @@ -0,0 +1,44 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/FilePackager.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" + +#include "OpenSHC/Globals/DAT_AIVDefinedData.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/FilePackagerObj.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004F14F0 + int AIVState::tryPlaceAIVAndReturnFitPercentage(int aivID, int castleID) + { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + sizeof(this->isHandled), 0, this->isHandled); + if (!MACRO_CALL_MEMBER(IO::FilePackager_Func::unpackAIVFile, FilePackagerObj::ptr)( + DAT_AIVDefinedData::instance.AIVSectionAddressArray, + DAT_AIVDefinedData::instance.AIVFileNames[this->aivs[aivID].aiType - 2][castleID])) { + return -3; + } + MACRO_CALL_MEMBER(AIVState_Func::rotateAIV, this)(this->aivs[aivID].keepOrientation); + int const fit = MACRO_CALL_MEMBER(AIVState_Func::computeAIVPlacementFit, this)(aivID); + if (fit <= 0) { + return -2; + } + this->aivs[aivID].castleID = castleID; + if (fit == 999999) { + // perfect fit + this->aivs[aivID].aivSubType = 2; + return 100; + } + this->aivs[aivID].aivSubType = 1; + // the counters are declared unsigned but used as signed values + int const buildingCounter = this->buildingCounter; + if (buildingCounter == 0) { + return 100; + } + return (buildingCounter - (int)this->invalidTileCounter) * 100 / buildingCounter; + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp b/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp new file mode 100644 index 000000000..0c208ec57 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/updateBuildingsStateAndUpdateAIBuildingDecisions.cpp @@ -0,0 +1,105 @@ +#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_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) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] == -1 + && DAT_GameSynchronyState::instance.currentAIArray[player] == 0) { + 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, this)(0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AIVState/wipeAIVsAndHeatMaps.cpp b/src/OpenSHC/AI/AIVState/wipeAIVsAndHeatMaps.cpp new file mode 100644 index 000000000..195d5d825 --- /dev/null +++ b/src/OpenSHC/AI/AIVState/wipeAIVsAndHeatMaps.cpp @@ -0,0 +1,22 @@ +#include "../AIVState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004EF090 + void AIVState::wipeAIVsAndHeatMaps() + { + this->aivCount = 0; + for (int aivID = 0; aivID < 9; ++aivID) { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + sizeof(this->aivs[aivID]), 0, &this->aivs[aivID]); + } + MACRO_CALL_MEMBER(AIVState_Func::clearTheHeatmaps, this)(); + } + +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b80b8635f..59be8904c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -37871,27 +37871,27 @@ SHC_3BB0A8C1_0x004ECA90 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004ECAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ECAD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECAD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ECBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECBC0 | 68.6% | orig branch shape; registers differ SHC_3BB0A8C1_0x004ECCA0 | 0.0% | Pending SHC_3BB0A8C1_0x004ECD90 | 0.0% | Pending -SHC_3BB0A8C1_0x004ECDB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECDB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004ECDE0 | 0.0% | Pending SHC_3BB0A8C1_0x004ECDF0 | 0.0% | Pending -SHC_3BB0A8C1_0x004ECE60 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECE60 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004ECEF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ECF70 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECF70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ECFE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ECFE0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004ED070 | 0.0% | Pending @@ -37923,47 +37923,47 @@ SHC_3BB0A8C1_0x004ED0A4 | 0.0% | Pending SHC_3BB0A8C1_0x004ED0A8 | 0.0% | Pending -SHC_3BB0A8C1_0x004ED0B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ED0B0 | 97.2% | Reimplemented -SHC_3BB0A8C1_0x004ED240 | 0.0% | Pending +SHC_3BB0A8C1_0x004ED240 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004ED300 | 0.0% | Pending +SHC_3BB0A8C1_0x004ED300 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ED3B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004ED3B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004ED410 | 0.0% | Pending +SHC_3BB0A8C1_0x004ED410 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EDC90 | 0.0% | Pending +SHC_3BB0A8C1_0x004EDC90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EDCB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EDCB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EDD00 | 0.0% | Pending +SHC_3BB0A8C1_0x004EDD00 | 98.8% | Reimplemented -SHC_3BB0A8C1_0x004EDDF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EDDF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EE140 | 0.0% | Pending +SHC_3BB0A8C1_0x004EE140 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004EE230 | 0.0% | Pending -SHC_3BB0A8C1_0x004EE430 | 0.0% | Pending +SHC_3BB0A8C1_0x004EE430 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EE830 | 0.0% | Pending +SHC_3BB0A8C1_0x004EE830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004EE920 | 0.0% | Pending -SHC_3BB0A8C1_0x004EEBD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EEBD0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004EECC0 | 0.0% | Pending -SHC_3BB0A8C1_0x004EEE10 | 0.0% | Pending +SHC_3BB0A8C1_0x004EEE10 | 88.2% | loop rotation; orig rematerial SHC_3BB0A8C1_0x004EEEC0 | 0.0% | Pending SHC_3BB0A8C1_0x004EEF20 | 0.0% | Pending -SHC_3BB0A8C1_0x004EF090 | 0.0% | Pending +SHC_3BB0A8C1_0x004EF090 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EF0D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EF0D0 | 67.2% | orig spills y in loop 1 SHC_3BB0A8C1_0x004EF130 | 0.0% | Pending @@ -37971,43 +37971,43 @@ SHC_3BB0A8C1_0x004EF150 | 0.0% | Pending SHC_3BB0A8C1_0x004EF450 | 0.0% | Pending -SHC_3BB0A8C1_0x004EF8C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EF8C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004EF900 | 0.0% | Pending SHC_3BB0A8C1_0x004EF920 | 0.0% | Pending -SHC_3BB0A8C1_0x004EFA60 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFA60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EFAB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFAB0 | 91.4% | Register reuse after search -SHC_3BB0A8C1_0x004EFB90 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFB90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EFC30 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFC30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EFD90 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFD90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EFE30 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFE30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004EFED0 | 0.0% | Pending +SHC_3BB0A8C1_0x004EFED0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004F0060 | 0.0% | Pending +SHC_3BB0A8C1_0x004F0060 | 53.6% | step < 50 fix; registers differ SHC_3BB0A8C1_0x004F0690 | 0.0% | Pending -SHC_3BB0A8C1_0x004F0E80 | 0.0% | Pending +SHC_3BB0A8C1_0x004F0E80 | 88.9% | rest is scheduling + a hoisted SHC_3BB0A8C1_0x004F1190 | 0.0% | Pending -SHC_3BB0A8C1_0x004F13E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004F13E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004F13F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004F13F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004F14F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004F14F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004F15C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004F15C0 | 98.5% | init stores sit after the aivID branch -SHC_3BB0A8C1_0x004F1860 | 0.0% | Pending +SHC_3BB0A8C1_0x004F1860 | 90.8% | orig has a redundant cmp eax,eax SHC_3BB0A8C1_0x004F1A60 | 0.0% | Pending