Skip to content

reimplement: AICState methods that are 100% - #261

Open
gynt wants to merge 2 commits into
mainfrom
reimpl/AICState-100
Open

gynt wants to merge 2 commits into
mainfrom
reimpl/AICState-100

Conversation

@gynt

@gynt gynt commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@gynt
gynt changed the base branch from main to reimpl/AICState-leftover October 14, 2026 07:49
@gynt
gynt changed the base branch from reimpl/AICState-leftover to main October 14, 2026 07:50
@gynt
gynt force-pushed the reimpl/AICState-100 branch from 959b9a9 to a8f7dae Compare October 14, 2026 08:15
// FUNCTION: STRONGHOLDCRUSADER 0x004CF270
void AICState::aiCommandTribe13DefensiveWave(int playerID)
{
int tribeIndex = 13;

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.

To be documented in the future

int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType;
if (aiType == AITA_NULL)
return 0;
return (&this->aics[aiType - 1].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex];

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.

The farms may be an array rather than farm1, farm2, etc. However, because the setAICParameters are not 100% (and that took long) I am sceptical I should change them to an array. I could make a union so we can treat it as an array or as fields, but I don't care that much at the moment, there are more important things. This code is pretty clear to me.

namespace AI {

// FUNCTION: STRONGHOLDCRUSADER 0x004CB3A0
undefined4 AICState::aiRequiresExtraOxtethers(int playerID)

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.

Should return BOOLEnum in the future.

{
int tribeID
= MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID);
for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) {

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.

The (int) cast is old code. maxUnitCount is already int in the headers. Remove cast.

continue;
if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3)
continue;
if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21)

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.

Magic value 21 is to be documented in the future

namespace AI {

// FUNCTION: STRONGHOLDCRUSADER 0x004CB940
undefined4 AICState::destroyBuildingIfNoWorker(int buildingID)

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.

BOOLEnum in the future

@TheRedDaemon TheRedDaemon left a comment

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.

First batch. Will continue review later.


int popularity = DAT_GameState::instance.playerDataArray[playerID].popularity;
if (popularity <= this->aics[aicIndex].criticalPopularity)
DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 2;

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.

Could it make sense to create enums for this, if this is state? Or are they "numerical" in meaning?

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.

YEs should become enums in the future.

if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 2) {
DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 2;
if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0)
DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0;

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.

Same thing with the ration settings: Enum or numerical?
The question can be extended to the taxes setting also, I think.

quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findNextBuildingForPlayerAndType,
DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY, quarryID)) {
int stone = DAT_BuildingsState::instance
.buildings[(short)DAT_BuildingsState::instance.buildings[quarryID].quarryStockpileID]

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.

Is this cast still needed?

continue;
if (DAT_UnitsState::instance.units[unitID].owner != playerID)
continue;
if (DAT_UnitsState::instance.units[unitID].dying != 0)

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.

Is this a boolean check?

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.

If so, we should use the if() and if(!) styles without the != 0

if (tribeID == 0
|| DAT_TribesState::instance.tribes[tribeID].uid
!= DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[baseOffset + offset]) {
if (checkOnly != FALSE) {

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.

Boolean check?

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.

If so, we should use the if() and if(!) styles without the != 0

@@ -0,0 +1,41 @@
#include "OpenSHC/Map/Units/UnitsState.func.hpp"
#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp"
#include "OpenSHC/AI/AICState.hpp"

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.

Wrong Import.

@@ -0,0 +1,36 @@
#include "OpenSHC/Map/Units/UnitsState.func.hpp"
#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp"
#include "OpenSHC/AI/AICState.hpp"

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.

Wrong Import

@@ -0,0 +1,23 @@
#include "OpenSHC/Map/Units/UnitsState.func.hpp"
#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp"
#include "OpenSHC/AI/AICState.hpp"

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.

Wrong Import.

@@ -0,0 +1,23 @@
#include "OpenSHC/Map/Units/UnitsState.func.hpp"
#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp"
#include "OpenSHC/AI/AICState.hpp"

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.

Wrong Import.
I stop at this point. You need to check all files again if they use the proper relative func.hpp Import.

// FUNCTION: STRONGHOLDCRUSADER 0x004D0BB0
void AICState::playGoodsSentBikFromPlayerToPlayer(int playerID, int targetPlayerID)
{
if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) {

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.

Can be transformed to truthiness checks.

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.

If so, we should use the if() and if(!) styles without the == 0

@TheRedDaemon TheRedDaemon left a comment

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.

Went through all.

int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1;
if (DAT_GameState::instance.mapAndTime.playerTeams[playerID]
== DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) {
bik = 23;

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.

The BIKs should likely also receive an enum. Athough, I assume you want to postpone this?

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.

Yea I really divide my time between reimplementing and doing research in Ghidra to develop enums, and currently I am not researching. It is also easy to solve in the future, we change the field type and compiler will complain in all included cpp files in which we still have a stray constant.

}

DWORD now = timeGetTime();
if (DWORD_00eb0b18::instance != 0 && now - DWORD_00eb0b18::instance < 1200000) {

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.

Is this used somewhere else? If not, then this should be the timestamp of the last time the thanks bik was played... although, does 20 min really seem right?

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.

Will become TIME_LastThanksBikPlaybackTime

using OpenSHC::WindowsHelper::Enums::BOOLEnum;

// FUNCTION: STRONGHOLDCRUSADER 0x004D30E0
undefined4 AICState::sendUnitsToAttackBreachedCastle(int attackingPlayerIndex)

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.

Is this also a BOOLEnum return?

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.

Nice spot, applied it in Ghidra.

Comment on lines +19 to +21
// FIXME: The original function never sets a return value (eax is left over from the last computation),
// so it was most likely a void function. The header declares int, which forces the "return 0"s below
// and costs an extra "xor eax, eax" in the assembly. Change the return type to void in the header.

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.

Is this still valid?

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.

Comment should be removed.

DAT_GameSynchronyState::ptr)(targetPlayer, 0);

// "Gold"
MACRO_CALL(OS_Func::_sprintf)(DAT_GameSynchronyState::instance.receivedChatMessage, "%d %s - ",

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.

Some non-replaced string?

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.

Literal strings should be replaced with the appropriate replacement in the src/OpenSHC/string-literals.hpp

}

candidateCount = 0;
// Players 1..8, manually unrolled by 4 in the original.

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.

So just having this:

                if ((DAT_GameSynchronyState::instance.currentAIArray[playerID] != 0)
                    && (DAT_GameState::instance.mapAndTime.playerTeams[playerID]
                        != DAT_GameState::instance.mapAndTime
                            .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) {
                    candidates[candidateCount] = playerID;
                    candidateCount++;
                }

in a loop from 1 to 8 does not unroll?

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.

I should try

{
for (int i = 0; i < 20; i++) {
MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)(
676, '\0', &this->aics[i]);

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.

It this sizeof the aic type?

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.

Yes! Should be replaced with a sizeof()

@@ -34157,73 +34157,73 @@ SHC_3BB0A8C1_0x004C6CD0 | 0.0% | Pending

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.

Does this still contains the percentages of the ones not part of the PR?

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.

Good point, no change should be made to the txt file lines that don't be long to any of the functions in this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants