Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmake/compiler-flags-gl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ src/OpenSHC/Audio/SFX/SFXState/playUnitSpeech.cpp
src/OpenSHC/Game/GameStateStructures/getSellResourceAmount.cpp
src/OpenSHC/Map/TileMapState/determineBuildingPlacementRotation.cpp
src/OpenSHC/Map/Navigation/DirectionAlgorithmState/getMouseVectorLengthBasedOnDirection.cpp
src/OpenSHC/Input/MouseState/updateRightDragCameraControl.cpp
src/OpenSHC/Input/MouseState/updateRightDragCameraControl.cpp

src/OpenSHC/AI/AIVState/executeDefaultCastleAIV.cpp
src/OpenSHC/AI/AIVState/findAppropriateGridLocationForBuilding.cpp
2 changes: 1 addition & 1 deletion src/OpenSHC/AI/AIVState.func.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ namespace AI {
int (AIVState::*)(), false, Address::SHC_3BB0A8C1_0x004EDD00, &AIVState::computeLargestSeparateArea)
computeLargestSeparateArea;

MACRO_FUNCTION_RESOLVER(int (AIVState::*)(int, MappersEnum), false, Address::SHC_3BB0A8C1_0x004EDDF0,
MACRO_FUNCTION_RESOLVER(BOOLEnum (AIVState::*)(int, MappersEnum), false, Address::SHC_3BB0A8C1_0x004EDDF0,
&AIVState::findSuitableFarmLocationAndPlaceFarm)
findSuitableFarmLocationAndPlaceFarm;

Expand Down
6 changes: 3 additions & 3 deletions src/OpenSHC/AI/AIVState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ namespace AI {
AIMapExtraInfo mapExtraInfo; // 0x0005CA14 length: 816
HeatMap heatMaps[80][80]; // 0x0005CD44 length: 307200
int visitCount; // 0x000A7D44 length: 4
undefined4 algAIndex; // 0x000A7D48 length: 4
undefined4 algBIndex; // 0x000A7D4C length: 4
int algAIndex; // 0x000A7D48 length: 4
int algBIndex; // 0x000A7D4C length: 4
int heatMapXArray[6400]; // 0x000A7D50 length: 25600
int heatMapYArray[6400]; // 0x000AE150 length: 25600
int buildingApproriateGridXPosition; // 0x000B4550 length: 4
Expand Down Expand Up @@ -112,7 +112,7 @@ namespace AI {

int computeLargestSeparateArea();

int findSuitableFarmLocationAndPlaceFarm(int playerID, MappersEnum commandBuildingType);
BOOLEnum findSuitableFarmLocationAndPlaceFarm(int playerID, MappersEnum commandBuildingType);

void findAppropriateWoodCutterGridLocation(int playerID);

Expand Down
14 changes: 14 additions & 0 deletions src/OpenSHC/AI/AIVState/Constructor_AIVState.cpp
Original file line number Diff line number Diff line change
@@ -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;
}

}
}
321 changes: 321 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceAIVBuilding.cpp

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceAdditionalOxtether.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#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(int 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),
// rotation 15 is a magic value: the building ends up with rotation 0
// unless DAT_TileMapState::skipPlacementCheck is set
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;
}

}
}
49 changes: 49 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceBrazier.cpp
Original file line number Diff line number Diff line change
@@ -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(int 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);
}
}

}
}
31 changes: 31 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceFarm.cpp
Original file line number Diff line number Diff line change
@@ -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)
!= FALSE;
}

}
}
38 changes: 38 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceIronMine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#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),
// rotation 15 is a magic value: the building ends up with rotation 0
// unless DAT_TileMapState::skipPlacementCheck is set
15);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these orientations enums or numerical?

@gynt gynt Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numerical with 15 being 0xf being an exception as it is a magic value: rotation isn't 15, but rather 0 if and only if skipPlacementCheck of DAT_TileMapState == 0.

}

}
}
38 changes: 38 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlacePitchrig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#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),
// rotation 15 is a magic value: the building ends up with rotation 0
// unless DAT_TileMapState::skipPlacementCheck is set
15);
}

}
}
77 changes: 77 additions & 0 deletions src/OpenSHC/AI/AIVState/aiPlaceQuarryAndOxtether.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#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),
// rotation 15 is a magic value: the building ends up with rotation 0
// unless DAT_TileMapState::skipPlacementCheck is set
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),
// rotation 15 is a magic value: the building ends up with rotation 0
// unless DAT_TileMapState::skipPlacementCheck is set
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;
}
}
}

}
}
18 changes: 18 additions & 0 deletions src/OpenSHC/AI/AIVState/clearTheHeatmaps.cpp
Original file line number Diff line number Diff line change
@@ -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);
}

}
}
Loading
Loading