From ea22350da868c81169b3a38dd20bb311bd1503c3 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 27 Sep 2026 00:22:28 +0200 Subject: [PATCH 001/104] reimplement: OpenSHC::AI::AICState --- cmake/compiler-flags-gl.txt | 3 +- .../AI/AICState/Constructor_AICState.cpp | 14 + .../AI/AICState/abortAttackAndGoIdle.cpp | 23 ++ .../addBuildingToTargetableBuildings.cpp | 30 ++ .../AI/AICState/addEngineersToSelection.cpp | 44 +++ .../AI/AICState/addEngineersToTribe.cpp | 33 ++ .../AI/AICState/addRallyPointForTribe.cpp | 57 ++++ src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp | 27 ++ .../addUnitToSmallestBehaviourTypeTribe.cpp | 79 +++++ .../AICState/addUnitToSmallestPatrolTribe.cpp | 27 ++ .../AI/AICState/addUnitsToTribe166.cpp | 35 +++ .../AICState/aiAddUnitToMoatDiggerTribe.cpp | 31 ++ .../AI/AICState/aiAssignMoatDiggers.cpp | 60 ++++ .../AICState/aiAssignUnitToDefensiveTribe.cpp | 26 ++ src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp | 28 ++ .../aiChoiceAttForceRallyPercentage.cpp | 45 +++ .../AICState/aiCommandSiegeEngineTribes.cpp | 100 +++++++ .../aiCommandTribe11StandGroundToGate.cpp | 35 +++ .../aiCommandTribe12AggressiveApproach.cpp | 58 ++++ .../aiCommandTribe13DefensiveWave.cpp | 32 ++ .../AICState/aiCommandWallAssaultTribes.cpp | 35 +++ .../AI/AICState/aiCreateSiegeUnits.cpp | 258 ++++++++++++++++ src/OpenSHC/AI/AICState/aiFarmChoice.cpp | 18 ++ .../AI/AICState/aiGiveOuterPatrolCommand.cpp | 77 +++++ .../AI/AICState/aiGiveRaidInstructions.cpp | 144 +++++++++ .../AI/AICState/aiOfTypeInCurrentGame.cpp | 25 ++ .../AICState/aiReassignTunnelersToTribe.cpp | 35 +++ .../AICState/aiRecruitEngineerForOilDuty.cpp | 71 +++++ .../AI/AICState/aiRecruitEngineers.cpp | 39 +++ .../AI/AICState/aiRecruitSortieMeleeUnits.cpp | 69 +++++ .../AICState/aiRecruitSortieRangedUnits.cpp | 70 +++++ src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 195 ++++++++++++ .../aiRegisterTribeAndAssignTarget.cpp | 37 +++ .../AI/AICState/aiRequiresExtraOxtethers.cpp | 65 ++++ src/OpenSHC/AI/AICState/aiRetreatUnits.cpp | 46 +++ ...iShouldAttackOrWaitForTeamCoordination.cpp | 80 +++++ src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp | 29 ++ .../AI/AICState/aiShouldBuildIronMine.cpp | 32 ++ .../AI/AICState/aiShouldBuildPitchrig.cpp | 32 ++ .../AI/AICState/aiShouldBuildQuarry.cpp | 32 ++ .../AI/AICState/aiShouldBuildWoodcutter.cpp | 41 +++ .../AI/AICState/aiUpdateTaxesAndRations.cpp | 91 ++++++ .../assignRequiredIdleEngineersToNewTribe.cpp | 49 +++ .../AI/AICState/assignUnitToATribe.cpp | 40 +++ src/OpenSHC/AI/AICState/buyGoods.cpp | 7 +- src/OpenSHC/AI/AICState/buyRequiredGoods.cpp | 58 ++++ .../canNavigateUnitsFromTileToTargetTile.cpp | 25 ++ ...mBuildingIDFromPlayersArrayOfBuildings.cpp | 43 +++ .../AI/AICState/clearOutdatedAITribes.cpp | 39 +++ .../clearPlayerDataInformationChunk.cpp | 22 ++ .../clearRallyFlagOnAllAIAttackTribes.cpp | 37 +++ .../AI/AICState/commandFourTribesToMove.cpp | 40 +++ .../AICState/computeAttackPossibilities.cpp | 38 +++ .../AICState/computeEnemyKeepApproachTile.cpp | 85 ++++++ .../AI/AICState/computeNervousness.cpp | 49 +++ .../AI/AICState/countSiegeEngineUnits.cpp | 36 +++ .../AI/AICState/createTribeForUnitType.cpp | 26 ++ .../AICState/decideOnTribeAttackLocation.cpp | 154 ++++++++++ .../AICState/destroyAnyBuildingInCategory.cpp | 97 ++++++ .../AI/AICState/destroyBuildingIfNoWorker.cpp | 44 +++ src/OpenSHC/AI/AICState/destroyHouse.cpp | 36 +++ ...determineAIPlayerAttackRequestResponse.cpp | 37 +++ .../AI/AICState/determineAIPlayerHelp.cpp | 36 +++ src/OpenSHC/AI/AICState/digNearestMoat.cpp | 54 ++++ .../AI/AICState/disbandAIPlayerSiegeUnits.cpp | 43 +++ ...ndAttackTribePositionAndPathToDefenses.cpp | 58 ++++ .../generateSiegeCreationInformation.cpp | 52 ++++ .../AI/AICState/getAIBuildInterval.cpp | 12 +- .../getCurrentDesiredAttackRaidUnitCount.cpp | 24 ++ .../AI/AICState/getDefensiveTribeForUnit.cpp | 53 ++++ .../AI/AICState/getResourceRebuildDelay.cpp | 18 ++ .../AI/AICState/getSmallestPatrolTribe.cpp | 45 +++ .../getTargetableBuildingForPlayerID.cpp | 48 +++ .../getUnitTypeForUnitBehaviourType.cpp | 57 ++++ .../giveMoveCommandToAttackTribes.cpp | 50 ++++ .../AICState/giveMoveCommandToSortieUnits.cpp | 71 +++++ .../AI/AICState/giveSomeRaidCommand.cpp | 45 +++ .../AICState/hasNoTroopsOrAllAreDiggers.cpp | 43 +++ .../AI/AICState/hasNotEnoughSupplies.cpp | 30 ++ .../AICState/ifHasTribe0SendUnitsToKeep.cpp | 22 ++ .../AI/AICState/instructTribe166ToMove.cpp | 69 +++++ ...qualThanMinimumGoodsRequiredAfterTrade.cpp | 25 ++ .../AI/AICState/makeOutpostTribesAttack.cpp | 22 ++ ...eUnitsGoDefensiveAndBackToSomeLocation.cpp | 47 +++ ...erIsWoodCuttersHutWhileNoWoodAvailable.cpp | 19 ++ .../AICState/moveAttackTribesToLocations.cpp | 47 +++ .../moveAttackingUnitsToNextRallyPoint.cpp | 71 +++++ ...rcentageNonMovingTribesGTEAICSpecified.cpp | 59 ++++ ...BuyWhenLowOnResourceAndSnoozeBuildings.cpp | 226 ++++++++++++++ .../AI/AICState/playAnger1BikFromPlayer.cpp | 41 +++ .../playAnger2BikFromPlayerToPlayer.cpp | 36 +++ .../playFriendlyVictoryBikFromPlayer.cpp | 23 ++ .../playGoodsNotSentBikFromPlayer.cpp | 23 ++ .../playGoodsRequestBikFromPlayerToPlayer.cpp | 32 ++ .../playGoodsSentBikFromPlayerToPlayer.cpp | 27 ++ .../AI/AICState/playJokeBikFromAIToHuman.cpp | 22 ++ .../AI/AICState/playNervous1BikFromPlayer.cpp | 27 ++ .../AI/AICState/playNervous2BikFromPlayer.cpp | 35 +++ .../AI/AICState/playNoHelpBikFromPlayer.cpp | 23 ++ ...yerKilledBiksFromKilledAndKillerPlayer.cpp | 73 +++++ .../AICState/playRequestHelpBikFromPlayer.cpp | 39 +++ ...playTauntToAttackedPlayerBikFromPlayer.cpp | 41 +++ .../AICState/playTeamWinningBikFromPlayer.cpp | 29 ++ .../playThanksBikFromPlayerToPlayer.cpp | 43 +++ .../playVictory2BikFromPlayerToPlayer.cpp | 43 +++ .../processAttForceRallyPercentage.cpp | 68 +++++ .../randomlySelectAttackUnitTypeToRecruit.cpp | 138 +++++++++ .../recomputeAIPathCostToAttackedKeep.cpp | 59 ++++ .../AI/AICState/recomputeAIZonerLayer.cpp | 31 ++ .../AI/AICState/recomputeAttackAIZone.cpp | 48 +++ .../AI/AICState/recreateEngineersTribe.cpp | 25 ++ .../recruitHarrassingSiegeEngines.cpp | 162 ++++++++++ ...oveOrganismsAndSetMoveDestinationPairs.cpp | 49 +++ .../AI/AICState/renderDebugDataAiInfo.cpp | 144 +++++++++ src/OpenSHC/AI/AICState/requestGoods.cpp | 70 +++++ .../AI/AICState/selectAttackTarget.cpp | 143 +++++++++ ...selectBuildingFromAListOfBuildingTypes.cpp | 35 +++ ...andomBuildingIDFromListOfBuildingTypes.cpp | 25 ++ src/OpenSHC/AI/AICState/sellExcessGoods.cpp | 105 +++++++ src/OpenSHC/AI/AICState/sellGoods.cpp | 1 - .../sendAITribe_0xf_toAttackPatrol.cpp | 38 +++ ...oAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp | 64 ++++ ...sendOilMenForOilPickupOrToAIVLocations.cpp | 62 ++++ ...gedUnitTribesToAIVSlotsOrKeepIfNervous.cpp | 58 ++++ .../AICState/sendTribeAtOffset1ToCampfire.cpp | 21 ++ .../AICState/sendTribeToAIVLocationSlot.cpp | 48 +++ src/OpenSHC/AI/AICState/sendTribeToAttack.cpp | 37 +++ .../sendUnitsToAttackBreachedCastle.cpp | 109 +++++++ .../AI/AICState/sendUnitsToCampfire.cpp | 24 ++ src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp | 24 ++ .../sendUnitsToPlayerIDsKeepIfPossible.cpp | 79 +++++ ...sendWallPatrolUnitTribesToAIVLocations.cpp | 94 ++++++ .../AI/AICState/setAICParameters_01.cpp | 150 ++++++++++ .../AI/AICState/setAICParameters_02.cpp | 148 +++++++++ .../AI/AICState/setAICParameters_03.cpp | 148 +++++++++ .../AI/AICState/setAICParameters_04.cpp | 149 +++++++++ .../AI/AICState/setAICParameters_05.cpp | 148 +++++++++ .../AI/AICState/setAICParameters_06.cpp | 150 ++++++++++ .../AI/AICState/setAICParameters_07.cpp | 147 +++++++++ .../AI/AICState/setAICParameters_08.cpp | 149 +++++++++ .../AI/AICState/setAICParameters_09.cpp | 146 +++++++++ .../AI/AICState/setAICParameters_10.cpp | 147 +++++++++ .../AI/AICState/setAICParameters_11.cpp | 145 +++++++++ .../AI/AICState/setAICParameters_12.cpp | 146 +++++++++ .../AI/AICState/setAICParameters_13.cpp | 142 +++++++++ .../AI/AICState/setAICParameters_14.cpp | 150 ++++++++++ .../AI/AICState/setAICParameters_15.cpp | 142 +++++++++ .../AI/AICState/setAICParameters_16.cpp | 144 +++++++++ .../AICState/setAttackCoordinationLevel.cpp | 33 ++ .../AI/AICState/setAttackPrimeInfo.cpp | 25 ++ .../setCurrentAttackRaidParameter.cpp | 40 +++ .../AI/AICState/setCurrentAttackStrength.cpp | 38 +++ ...htTribe0xc0TribesToAggressiveAndAttack.cpp | 38 +++ src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp | 26 +- src/OpenSHC/AI/AICState/setInitialAIC.cpp | 45 +++ .../AI/AICState/setNextFarmTypeToBuild.cpp | 25 ++ .../AICState/setNextMoveLocationForUnits.cpp | 38 +++ ...TargetForTribeBasedOnTargetChoiceLogic.cpp | 106 +++++++ ...x11ToAggressiveAndAttackIfConditionMet.cpp | 41 +++ .../setTribe0xCtoAggressiveAndAttack.cpp | 37 +++ .../setTribe0xbToAggressiveAndAttack.cpp | 37 +++ .../AICState/setTriggerForAITauntResponse.cpp | 17 ++ .../AICState/setupWorkshopProductionType.cpp | 85 ++++++ .../AI/AICState/shareGoldAmongTeamMembers.cpp | 77 +++++ .../AICState/shouldDestroyWoodCutterUnk.cpp | 52 ++++ .../AICState/shouldNotBeBuildingArmoury.cpp | 18 ++ .../AICState/shouldNotBeBuildingGranary.cpp | 18 ++ .../AI/AICState/shouldNotBeBuildingHovel.cpp | 36 +++ .../AI/AICState/shouldNotBuildKillingPits.cpp | 18 ++ ...rketArmoryGranaryBarracksMercenarypost.cpp | 29 ++ ...uldWaitWithBuildingAPositiveFearFactor.cpp | 50 ++++ ...ouldWaitWithBuildingNegativeFearFactor.cpp | 39 +++ .../AI/AICState/smallestTribeOfUnitType.cpp | 62 ++++ .../AI/AICState/someChatMessageSelection.cpp | 127 ++++++++ .../AICState/syncBehaviourType7UnitTribes.cpp | 35 +++ .../AI/AICState/teamIsWeakRelativeToEnemy.cpp | 52 ++++ .../AI/AICState/unitIDIsRangedOrArmored.cpp | 27 ++ src/OpenSHC/AI/AICState/updateAIBehaviour.cpp | 94 ++++++ .../AI/AICState/updateAIPlayerState.cpp | 283 ++++++++++++++++++ .../AI/AICState/updateAIStrengthState.cpp | 212 +++++++++++++ .../AI/AICState/updateTribeLocations.cpp | 58 ++++ ...12_toPlaceSiegeTentsAndAssignEngineers.cpp | 105 +++++++ .../useAITribe_0xe_toPlaceTunnels.cpp | 67 +++++ src/OpenSHC/AI/AICState/wipeAICMemory.cpp | 18 ++ src/OpenSHC/AI/AICState/yieldCowThrow.cpp | 29 ++ 185 files changed, 11397 insertions(+), 25 deletions(-) create mode 100644 src/OpenSHC/AI/AICState/Constructor_AICState.cpp create mode 100644 src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp create mode 100644 src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp create mode 100644 src/OpenSHC/AI/AICState/addEngineersToSelection.cpp create mode 100644 src/OpenSHC/AI/AICState/addEngineersToTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp create mode 100644 src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiFarmChoice.cpp create mode 100644 src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp create mode 100644 src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp create mode 100644 src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp create mode 100644 src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRecruitUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp create mode 100644 src/OpenSHC/AI/AICState/aiRetreatUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp create mode 100644 src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp create mode 100644 src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp create mode 100644 src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/assignUnitToATribe.cpp create mode 100644 src/OpenSHC/AI/AICState/buyRequiredGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp create mode 100644 src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp create mode 100644 src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp create mode 100644 src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp create mode 100644 src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp create mode 100644 src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp create mode 100644 src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp create mode 100644 src/OpenSHC/AI/AICState/computeNervousness.cpp create mode 100644 src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/createTribeForUnitType.cpp create mode 100644 src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp create mode 100644 src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp create mode 100644 src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp create mode 100644 src/OpenSHC/AI/AICState/destroyHouse.cpp create mode 100644 src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp create mode 100644 src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp create mode 100644 src/OpenSHC/AI/AICState/digNearestMoat.cpp create mode 100644 src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp create mode 100644 src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp create mode 100644 src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp create mode 100644 src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp create mode 100644 src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp create mode 100644 src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp create mode 100644 src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp create mode 100644 src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp create mode 100644 src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp create mode 100644 src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp create mode 100644 src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp create mode 100644 src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp create mode 100644 src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp create mode 100644 src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp create mode 100644 src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp create mode 100644 src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp create mode 100644 src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp create mode 100644 src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp create mode 100644 src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp create mode 100644 src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp create mode 100644 src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp create mode 100644 src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp create mode 100644 src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp create mode 100644 src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp create mode 100644 src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp create mode 100644 src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp create mode 100644 src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp create mode 100644 src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp create mode 100644 src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp create mode 100644 src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp create mode 100644 src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp create mode 100644 src/OpenSHC/AI/AICState/requestGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/selectAttackTarget.cpp create mode 100644 src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp create mode 100644 src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp create mode 100644 src/OpenSHC/AI/AICState/sellExcessGoods.cpp create mode 100644 src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp create mode 100644 src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp create mode 100644 src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp create mode 100644 src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp create mode 100644 src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp create mode 100644 src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp create mode 100644 src/OpenSHC/AI/AICState/sendTribeToAttack.cpp create mode 100644 src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp create mode 100644 src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp create mode 100644 src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp create mode 100644 src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp create mode 100644 src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_01.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_02.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_03.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_04.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_05.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_06.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_07.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_08.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_09.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_10.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_11.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_12.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_13.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_14.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_15.cpp create mode 100644 src/OpenSHC/AI/AICState/setAICParameters_16.cpp create mode 100644 src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp create mode 100644 src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp create mode 100644 src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp create mode 100644 src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp create mode 100644 src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp create mode 100644 src/OpenSHC/AI/AICState/setInitialAIC.cpp create mode 100644 src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp create mode 100644 src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp create mode 100644 src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp create mode 100644 src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp create mode 100644 src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp create mode 100644 src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp create mode 100644 src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp create mode 100644 src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp create mode 100644 src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp create mode 100644 src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp create mode 100644 src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp create mode 100644 src/OpenSHC/AI/AICState/someChatMessageSelection.cpp create mode 100644 src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp create mode 100644 src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp create mode 100644 src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp create mode 100644 src/OpenSHC/AI/AICState/updateAIBehaviour.cpp create mode 100644 src/OpenSHC/AI/AICState/updateAIPlayerState.cpp create mode 100644 src/OpenSHC/AI/AICState/updateAIStrengthState.cpp create mode 100644 src/OpenSHC/AI/AICState/updateTribeLocations.cpp create mode 100644 src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp create mode 100644 src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp create mode 100644 src/OpenSHC/AI/AICState/wipeAICMemory.cpp create mode 100644 src/OpenSHC/AI/AICState/yieldCowThrow.cpp diff --git a/cmake/compiler-flags-gl.txt b/cmake/compiler-flags-gl.txt index d0c81b8d5..fe4000b4b 100644 --- a/cmake/compiler-flags-gl.txt +++ b/cmake/compiler-flags-gl.txt @@ -16,4 +16,5 @@ 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 \ No newline at end of file +src/OpenSHC/Input/MouseState/updateRightDragCameraControl.cpp +src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp diff --git a/src/OpenSHC/AI/AICState/Constructor_AICState.cpp b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp new file mode 100644 index 000000000..2eedf70e1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp @@ -0,0 +1,14 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3940 + AICState* AICState::Constructor_AICState() + { + MACRO_CALL_MEMBER(AICState_Func::setInitialAIC, this)(); + this->aiTauntResponseTimeWindow = 0; + return this; + } +} +} diff --git a/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp new file mode 100644 index 000000000..bdd9f7312 --- /dev/null +++ b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp @@ -0,0 +1,23 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D45E0 + void AICState::abortAttackAndGoIdle(int playerID) + { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp new file mode 100644 index 000000000..bd48cbe1c --- /dev/null +++ b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp @@ -0,0 +1,30 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDA50 + void AICState::addBuildingToTargetableBuildings(int buildingID) + { + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return; + if (DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker >= 100) + return; + if (DAT_BuildingsState::instance.buildings[buildingID].fireDuration != 0) + return; + + DAT_GameState::instance.playerDataArray[owner] + .top100TargetableBuildings[DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker] + = buildingID; + DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker++; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp new file mode 100644 index 000000000..3aae5e97a --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp @@ -0,0 +1,44 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC680 + int AICState::addEngineersToSelection(int playerID, int unitCount) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (unitCount <= 0) + return tribeID; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 10) + continue; + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].state.generic == Map::Units::States::US_STAND_UPUnk) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + unitCount--; + } + return tribeID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp new file mode 100644 index 000000000..48bc61b6a --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp @@ -0,0 +1,33 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4040 + void AICState::addEngineersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 10) + continue; + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + continue; + + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 10); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp new file mode 100644 index 000000000..5a8527736 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp @@ -0,0 +1,57 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFA10 + void AICState::addRallyPointForTribe(int tribeID) + { + DAT_TribesState::instance.tribes[tribeID].rallyPointCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getTeamsDifferent, DAT_GameState::ptr)( + DAT_TribesState::instance.tribes[tribeID].owner, DAT_UnitsState::instance.units[unitID].owner) + != FALSE) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeNextRallyPointDestination, + DAT_PathFindingState::ptr)( + -1, DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y); + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][0] + = DAT_PathFindingState::instance.ALG_ResultX; + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][1] + = DAT_PathFindingState::instance.ALG_ResultY; + DAT_TribesState::instance.tribes[tribeID].rallyPointCount++; + if (DAT_TribesState::instance.tribes[tribeID].rallyPointCount >= 9) + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex < 0) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + >= DAT_TribesState::instance.tribes[tribeID].rallyPointCount) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp new file mode 100644 index 000000000..0cf637150 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D27E0 + void AICState::addUnitToItsTribe(int unitID, int aiUnitBehaviourType) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = aiUnitBehaviourType; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::addUnitToSmallestBehaviourTypeTribe, this)( + owner, unitID, aiUnitBehaviourType); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp new file mode 100644 index 000000000..50144e366 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -0,0 +1,79 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCD20 + int AICState::addUnitToSmallestBehaviourTypeTribe( + undefined4 playerID, undefined4 unitID, undefined4 aiUnitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + + int aicIndex = aiType - 1; + int tribeType = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + int groupsCount; + switch (tribeType) { + case 15: + groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; + break; + case 186: + groupsCount = this->aics[aicIndex].AttUnitBackupGroupsCount; + break; + case 190: + groupsCount = this->aics[aicIndex].AttUnitSiegeDefGroupsCount; + break; + case 192: + groupsCount = this->aics[aicIndex].AttMainGroupsCount; + break; + default: + groupsCount = 1; + break; + } + + int selectedTribeID = 0; + int selectedSlot = 0; + if (groupsCount <= 1) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeType]) + return tribeID; + selectedTribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = tribeType; + } else { + int smallestSize = 1000; + for (int slot = tribeType; slot < tribeType + groupsCount; slot++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[slot]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[slot]) { + selectedTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = slot; + break; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedSlot = slot; + } + } + } + if (selectedTribeID <= 0) + return 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedSlot] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedSlot] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp new file mode 100644 index 000000000..40602a5c4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2730 + void AICState::addUnitToSmallestPatrolTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + int aiType = DAT_GameState::instance.playerDataArray[owner].aiType; + if (aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 4; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getSmallestPatrolTribe, this)( + owner, this->aics[aiType - 1].OuterPatrolGroupsCount); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp new file mode 100644 index 000000000..5e0cbafd8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC720 + void AICState::addUnitsToTribe166(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 6) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp new file mode 100644 index 000000000..4857156f9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp @@ -0,0 +1,31 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC840 + void AICState::aiAddUnitToMoatDiggerTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 5; + int tribeID = DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10]) { + tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(owner); + DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10] = tribeID; + DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10] + = DAT_TribesState::instance.tribes[tribeID].uid; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp new file mode 100644 index 000000000..6821d3933 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp @@ -0,0 +1,60 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3F20 + void AICState::aiAssignMoatDiggers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType0 <= 0) + return; + + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].isSelectable_OR_matchTime == 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_LORD + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ENGINEER + || DAT_UnitsState::instance.units[unitID].unitType == UT_TUNNELER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TREBUCHET + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_MANGONEL + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_SHIELD + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BALLISTA + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_FBALLISTA) + continue; + + if (this->aics[aicIndex].DefDiggingUnitMax == 0 + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ARCHER + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_XBOW + || DAT_UnitsState::instance.units[unitID].someUnitStat4 == 0) + MACRO_CALL_MEMBER(AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp new file mode 100644 index 000000000..bd7a41dec --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp @@ -0,0 +1,26 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2790 + void AICState::aiAssignUnitToDefensiveTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 2; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getDefensiveTribeForUnit, this)(owner, unitID); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp new file mode 100644 index 000000000..2d81632a7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp @@ -0,0 +1,28 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D48F0 + void AICState::aiBuyAndSellGoods(int playerID) + { + int marketID = DAT_GameState::instance.playerDataArray[playerID].marketplace.id; + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 0; + if (marketID != 0) + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 1; + if (DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk == 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].willBuy + = DAT_GameState::instance.playerDataArray[playerID].willBuy ^ TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].willBuy == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellExcessGoods, this)(playerID); + return; + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyRequiredGoods, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp new file mode 100644 index 000000000..890450502 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEC80 + BOOLEnum AICState::aiChoiceAttForceRallyPercentage(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int activeTribes = 0; + int rallyingTribes = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + activeTribes++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) + rallyingTribes++; + } + } + + if (activeTribes <= 0) + return FALSE; + return rallyingTribes * 100 / activeTribes >= this->aics[aicIndex].AttForceRallyPercentage; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp new file mode 100644 index 000000000..8032d413f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -0,0 +1,100 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFCD0 + void AICState::aiCommandSiegeEngineTribes(int playerID) + { + int shieldTargetTribeIDs[3]; + shieldTargetTribeIDs[0] = 0; + shieldTargetTribeIDs[1] = 0; + shieldTargetTribeIDs[2] = 0; + int targetCount = 0; + for (int i = 0; i < 3; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[186 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[186 + i]) + continue; + shieldTargetTribeIDs[targetCount] = tribeID; + targetCount++; + } + + int shieldCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != UT_S_TOWER + && DAT_UnitsState::instance.units[unitID].unitType != UT_S_BATTERINGRAM + && DAT_UnitsState::instance.units[unitID].unitType != UT_S_SHIELD) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER) { + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x414); + } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM) { + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.gateValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x3f6); + else if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x413); + } else { + if (shieldCount >= 3) + continue; + + int targetTribeID = shieldTargetTribeIDs[shieldCount]; + DAT_TribesState::instance.tribes[tribeID].field56_0x1f2 = targetTribeID; + DAT_TribesState::instance.tribes[tribeID].uid2 = DAT_TribesState::instance.tribes[targetTribeID].uid; + int targetUnitID = DAT_TribesState::instance.tribes[targetTribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[targetTribeID].tribeID = tribeID; + int tile = DAT_UnitsState::instance.units[targetUnitID].tile; + shieldCount++; + if (tile <= 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, Instructions::UMSE_0); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::calculatePreferredRelativeOrientation, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + DAT_TribesState::instance.tribes[tribeID].orientation + = DAT_DirectionAlgorithmState::instance.orientation; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp new file mode 100644 index 000000000..f4f46c614 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF470 + void AICState::aiCommandTribe11StandGroundToGate(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + if (MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTribeToNearbyGatehouse, DAT_TroopValueState::ptr)( + tribeID) + == 0) + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTowardsParticularUnits, DAT_TroopValueState::ptr)( + tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp new file mode 100644 index 000000000..b82c43b75 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF2E0 + void AICState::aiCommandTribe12AggressiveApproach(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) + return; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, -1, -1, 10000, FALSE); + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(attackedPlayerID, 200) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp new file mode 100644 index 000000000..b921b42ae --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF270 + void AICState::aiCommandTribe13DefensiveWave(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[13]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[13]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f4); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp new file mode 100644 index 000000000..a1b457e72 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF660 + void AICState::aiCommandWallAssaultTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + + for (int i = 0; i < 2; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[190 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 190]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp new file mode 100644 index 000000000..7e51f30d5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -0,0 +1,258 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2000 + void AICState::aiCreateSiegeUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + // The original also processes index 4, one past the end of SiegeEngineMetaInfoArray. + for (int siegeIndex = 0; siegeIndex < 5; siegeIndex++) { + UnitType unitType + = (UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; + int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; + int locationCount + = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + if (locationCount >= 4) + locationCount = 3; + + for (int i = 0; i < locationCount; i++) { + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i]; + if (location <= 0) + continue; + + int existingBuildingID = (short)DAT_TileMapState::instance.BuildingLayer[location]; + int unitID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID; + int uid + = DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid; + int buildingID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID; + + if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + if (buildingID != 0 && DAT_BuildingsState::instance.buildings[buildingID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + + if (unitID == 0) { + if (buildingID != 0) { + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount + == requiredEngineers) + continue; + if (requiredEngineers + > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, + DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + + if (existingBuildingID == 0) { + if (siegeIndex < 2) + continue; + } else if (siegeIndex < 2) { + if (DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + != OpenSHC::Map::Buildings::BT_TOWER4 + && DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + != OpenSHC::Map::Buildings::BT_TOWER5) + continue; + } + + int requiredGold + = DAT_BuildingsState::instance + .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType] + .requiredGold; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + + this->aics[aiType - 1].defSiegeEngineGoldThreshold + < requiredGold) + continue; + if (DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .delay + < this->aics[aiType - 1].defSiegeEngineBuildDelay) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[location]; + int x = location - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (siegeIndex < 2) { + int spawnedUnitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::spawnUnit, + DAT_UnitsState::ptr)(playerID, playerID, x * 8 + 4, y * 8 + 4, + DAT_BuildingsState::instance.buildings[existingBuildingID].terrainHeightUnk, unitType); + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = spawnedUnitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[spawnedUnitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::processPlacementResourceLossForBuildingType, + DAT_BuildingsState::ptr)(playerID, + (BuildingType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType, + 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)( + playerID, x, y, + (MappersEnum)(ushort)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .commandBuildingType, + 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail == FALSE) { + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= requiredGold; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .uid + = DAT_BuildingsState::instance.buildings[DAT_TileMapState::instance.placedBuildingID] + .uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = DAT_TileMapState::instance.placedBuildingID; + } + } + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + return; + } + + if (unitID != 0) { + if (buildingID != 0) { + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount + == requiredEngineers) + continue; + if (requiredEngineers + > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, + DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + int remainingEngineers + = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, + DAT_UnitsState::ptr)(unitID); + if (remainingEngineers <= 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, remainingEngineers); + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + 0) + == 0) + continue; + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + return; + } + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiFarmChoice.cpp b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp new file mode 100644 index 000000000..46898001b --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB180 + int AICState::aiFarmChoice(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return (&this->aics[aiType - 1].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp new file mode 100644 index 000000000..1e8ad2f21 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -0,0 +1,77 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D28B0 + void AICState::aiGiveOuterPatrolCommand(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int aicIndex = aiType - 1; + int groupsCount = this->aics[aicIndex].OuterPatrolGroupsCount; + if (groupsCount <= 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker + < this->aics[aicIndex].OuterPatrolRallyDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker = 0; + + for (int i = 0; i < groupsCount; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageShootingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10) + continue; + + int buildingID = 0; + if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) + buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); + int tile; + if (buildingID != 0) { + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + buildingID = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) != FALSE) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + else + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp new file mode 100644 index 000000000..2f1047fba --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2A70 + void AICState::aiGiveRaidInstructions(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter + >= this->aics[aicIndex].RaidRetargetDelay) + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk == 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown > 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown--; + + for (int i = 0; i < 6; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[180 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 180]) + continue; + + int buildingID = 0; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + != 0) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown == 0 + && DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk < MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)(tribeID, i, TRUE) + != FALSE) + continue; + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, this)(playerID, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 182 + || DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 181) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + } else { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + targetBuildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + if (targetBuildingID == 0) { + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + continue; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, + DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + if (buildingID == 0) + continue; + int tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + == FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp new file mode 100644 index 000000000..b8de9aefa --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0580 + BOOLEnum AICState::aiOfTypeInCurrentGame(int aicIndex) + { + for (int playerID = 1; playerID < 9; playerID++) { + if (DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].aiType - 1 != aicIndex) + continue; + return TRUE; + } + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp new file mode 100644 index 000000000..6165dd546 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D40B0 + void AICState::aiReassignTunnelersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_TUNNELER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 15) + continue; + + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 15); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp new file mode 100644 index 000000000..8c7e889a5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp @@ -0,0 +1,71 @@ +#include "OpenSHC/AI/AICState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2500 + void AICState::aiRecruitEngineerForOilDuty(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 30) + return; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + int smelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + if (smelterID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[smelterID].buildingType != Map::Buildings::BT_OILSMELTER) + return; + int amount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1] + 1; + if (amount <= 1) + return; + + if (DAT_BuildingsState::instance.buildings[smelterID].resources[Game::Resources::RT_PITCH] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_PITCH] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_PITCH] = 4; + return; + } + if (MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, TRUE) + == FALSE) + return; + + int tribeID = MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, FALSE); + int unitID = MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id, playerID, + 0); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + if (unitID == 0) + return; + + DAT_UnitsState::instance.units[unitID].engineerRelatedUnk = 1; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeID].tribeType = Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = Map::Units::STBT_0x41d; + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)(tribeID, + Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, smelterID, + DAT_BuildingsState::instance.buildings[smelterID].uid, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp new file mode 100644 index 000000000..23c4bf441 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC520 + void AICState::aiRecruitEngineers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired == FALSE) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (this->aics[aiType - 1].recruitGoldThreshold + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < 30) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, guildID, playerID, 0); + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp new file mode 100644 index 000000000..c3ae30af2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp @@ -0,0 +1,69 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD690 + void AICState::aiRecruitSortieMeleeUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitMeleeMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 + >= this->aics[aicIndex].SortieUnitMeleeMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitMelee; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 7; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 167)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp new file mode 100644 index 000000000..951822739 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD560 + void AICState::aiRecruitSortieRangedUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitRangedMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 + >= DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 / 2 + + this->aics[aicIndex].SortieUnitRangedMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitRanged; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 6; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 166)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp new file mode 100644 index 000000000..0bcda0b49 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -0,0 +1,195 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3AE0 + void AICState::aiRecruitUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + bool isExtremeTrail = DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME; + + int recruitInterval = (&this->aics[aicIndex] + .RecruitInterval)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int recruitCount = recruitInterval != 0 ? 1 : 2; + if (isExtremeTrail) + recruitCount++; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker < recruitInterval) + return; + } else { + recruitCount = 4; + } + + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker = 0; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + int defTotal = this->aics[aicIndex].DefTotal; + if (isExtremeTrail) + defTotal = defTotal * 4 / 3; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) + defTotal = defTotal * 4; + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk >= defTotal) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING + && DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + return; + } + + bool recruitDiggingUnit = false; + if (this->aics[aicIndex].DefDiggingUnitMax != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)(playerID) + != 0) { + int diggerTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (diggerTribeID == 0 + || DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10] + != DAT_TribesState::instance.tribes[diggerTribeID].uid + || DAT_TribesState::instance.tribes[diggerTribeID].size < this->aics[aicIndex].DefDiggingUnitMax) + recruitDiggingUnit = true; + } + + UnitType unitType = (UnitType)0; + int behaviourType = 0; + for (int i = 0; i < recruitCount; i++) { + if (recruitDiggingUnit && (SEC_RNG::instance.currentNumber2 & 1) != 0) + recruitDiggingUnit = false; + + if (recruitDiggingUnit) { + if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id == 0) + return; + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + int choice = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex; + unitType = (UnitType)(&this->aics[aicIndex].DefUnit1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = choice + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + int choice = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex; + unitType = (UnitType)(&this->aics[aicIndex].RaidUnit1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = choice + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + behaviourType = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::randomlySelectAttackUnitTypeToRecruit, this)(playerID); + unitType = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getUnitTypeForUnitBehaviourType, this)( + playerID, (AIUnitBehaviourType)behaviourType); + } + if (unitType == (UnitType)0) + return; + + int buildingID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].tunnelersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_MONK) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_CATHEDRAL); + else if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER || unitType == OpenSHC::Map::Units::UT_E_MONK + || unitType >= 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment <= 0) + return; + if (DAT_UnitsState::instance.euroUnitAcquisitionFailReason != 2) + return; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + else + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + return; + } + + if (recruitDiggingUnit) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + < this->aics[aicIndex].DefWalls) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToSmallestPatrolTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignUnitToDefensiveTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToItsTribe, this)(unitID, behaviourType); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp new file mode 100644 index 000000000..1d2272d15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1950 + void AICState::aiRegisterTribeAndAssignTarget(int tribeID, int tribeUID) + { + if (DAT_TribesState::instance.tribes[tribeID].size > 0) { + if (DAT_GameState::instance.playerDataArray[DAT_TribesState::instance.tribes[tribeID].owner].aiType + == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 1000; i++) { + int existingTribeID = this->tribeIDArray[i]; + if (existingTribeID != 0 + && DAT_TribesState::instance.tribes[existingTribeID].uid == this->tribeUIDArray[i]) + continue; + + this->tribeIDArray[i] = tribeID; + this->tribeUIDArray[i] = tribeUID; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTargetForTribeBasedOnTargetChoiceLogic, this)( + DAT_TribesState::instance.tribes[tribeID].owner, tribeID); + return; + } + return; + } + DAT_TribesState::instance.tribes[tribeID].tribeState = 3; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp new file mode 100644 index 000000000..7117f018c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp @@ -0,0 +1,65 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB3A0 + undefined4 AICState::aiRequiresExtraOxtethers(int playerID) + { + int quarryID + = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY); + int highestStonePerTether = 0; + int highestQuarryID = 0; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_OXTETHER, 1) + >= 10) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = 0; + if (quarryID == 0) + return 0; + + for (; quarryID != 0; + quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findNextBuildingForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY, quarryID)) { + int stone = DAT_BuildingsState::instance + .buildings[(short)DAT_BuildingsState::instance.buildings[quarryID].quarryStockpileID] + .resources[Game::Resources::RT_STONE]; + int tetherCount = 0; + for (int i = 0; i < 3; i++) { + int tetherID = DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i]; + if (tetherID == 0) + continue; + if (DAT_BuildingsState::instance.buildings[tetherID].buildingType + == OpenSHC::Map::Buildings::BT_OXTETHER + && DAT_BuildingsState::instance.buildings[tetherID].oxtetherLinkedQuarryID == quarryID) + tetherCount++; + else + DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i] = 0; + } + + if (tetherCount <= 0) { + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = quarryID; + return 1; + } + if (stone / tetherCount > highestStonePerTether) { + highestQuarryID = quarryID; + highestStonePerTether = stone / tetherCount; + } + } + + if (highestStonePerTether <= 20) + return 0; + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = highestQuarryID; + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp new file mode 100644 index 000000000..7cdacce2c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp @@ -0,0 +1,46 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE4D0 + void AICState::aiRetreatUnits(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeType == 190) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (tribeType == 13) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::selectionContainsHorses, + DAT_TribesState::ptr)(tribeID) + != FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp new file mode 100644 index 000000000..1004fb6c6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -0,0 +1,80 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCF0 + BOOLEnum AICState::aiShouldAttackOrWaitForTeamCoordination(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + return TRUE; + + if (DAT_GameState::instance.playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .totalTroopValue + <= 100) + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceBase + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel == 0) + return TRUE; + + int readyTeamMembers = 0; + int unreadyTeamMembers = 0; + for (int otherPlayerID = 1; otherPlayerID < 9; otherPlayerID++) { + if (otherPlayerID == playerID) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + otherPlayerID) + == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::isAIPlayer, DAT_GameSynchronyState::ptr)( + otherPlayerID) + == FALSE) + continue; + if (DAT_GameState::instance.mapAndTime.playerTeams[otherPlayerID] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) + continue; + + if (DAT_GameState::instance.playerDataArray[otherPlayerID].aiPlayerState != 0) + return TRUE; + if (DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackingEngineerTroops + >= this->aics[DAT_GameState::instance.playerDataArray[otherPlayerID].aiType - 1].AttForceBase + + DAT_GameState::instance.playerDataArray[otherPlayerID].currentWaveRandomAttackingStrength) + readyTeamMembers++; + else + unreadyTeamMembers++; + } + + if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel) + return TRUE; + if (unreadyTeamMembers == 0) + return TRUE; + + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience > 48 + && DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_CAMPAIGN_MISSION) + return TRUE; + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp new file mode 100644 index 000000000..02ab1e06c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB220 + BOOLEnum AICState::aiShouldBuildFarm(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int farmCount + = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countFarms, DAT_BuildingsState::ptr)(playerID, 1); + if (farmCount <= 0) + return TRUE; + if (farmCount >= this->aics[aicIndex].maxFarms) + return FALSE; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / farmCount + >= this->aics[aicIndex].populationPerFarm; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp new file mode 100644 index 000000000..1f0c072e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB4D0 + uint AICState::aiShouldBuildIronMine(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerIronmine <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_IRONMINE, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxIronmines) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerIronmine; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp new file mode 100644 index 000000000..5cf71d7e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB540 + uint AICState::aiShouldBuildPitchrig(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerPitchrig <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_PITCHRIG, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxPitchrigs) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerPitchrig; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp new file mode 100644 index 000000000..2fcefd9ab --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB330 + uint AICState::aiShouldBuildQuarry(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerQuarry <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_QUARRY, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxQuarries) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerQuarry; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp new file mode 100644 index 000000000..7d223c5a9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB290 + BOOLEnum AICState::aiShouldBuildWoodcutter(int playerID) + { + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return FALSE; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerWoodcutter <= 0) + return FALSE; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT, 1); + if (count <= 0) { + count = 1; + } else { + if (count >= this->aics[aicIndex].maxWoodcutters) + return FALSE; + if (count >= 6 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 90) + return FALSE; + if (count >= 3 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 30) + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerWoodcutter; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp new file mode 100644 index 000000000..cd1b58035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp @@ -0,0 +1,91 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAEA0 + void AICState::aiUpdateTaxesAndRations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + int popularity = DAT_GameState::instance.playerDataArray[playerID].popularity; + if (popularity <= this->aics[aicIndex].criticalPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 2; + else if (popularity <= this->aics[aicIndex].lowestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 1; + else if (popularity >= this->aics[aicIndex].highestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 2) { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 2; + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 8) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 1) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > this->aics[aicIndex].taxesMin) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + else + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMin; + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < this->aics[aicIndex].taxesMax) { + int lowerTaxesLimit; + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 15) + lowerTaxesLimit = 6; + else if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 10) + lowerTaxesLimit = 5; + else + lowerTaxesLimit = 4; + + if (DAT_GameState::instance.playerDataArray[playerID].storedPopularityPercent <= popularity) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting++; + else if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > lowerTaxesLimit) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + } else { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMax; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + } else { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } + } + + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > 11) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 11; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting > 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp new file mode 100644 index 000000000..d6f813898 --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp @@ -0,0 +1,49 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC5A0 + int AICState::assignRequiredIdleEngineersToNewTribe(int playerID, int count) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (count <= 0) + return tribeID; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 22) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 10) + continue; + if (DAT_UnitsState::instance.units[unitID].engineerRelatedUnk != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].state.generic != 105 + && DAT_UnitsState::instance.units[unitID].state.generic != 2 + && DAT_UnitsState::instance.units[unitID].state.generic != Map::Units::States::US_IDLEUnk) + continue; + + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + count--; + } + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp new file mode 100644 index 000000000..9f320a41b --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp @@ -0,0 +1,40 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2660 + void AICState::assignUnitToATribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + AIVUnitType aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, + DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[owner].aivPauses[1] > 0 + ? 1 + : 0); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 1; + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlotLocationCount[aivUnitType] <= 0) { + if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) { + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 0)); + return; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 1)); + return; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(AICState_Func::smallestTribeOfUnitType, this)(owner, aivUnitType)); + } +} +} diff --git a/src/OpenSHC/AI/AICState/buyGoods.cpp b/src/OpenSHC/AI/AICState/buyGoods.cpp index 7075afd56..479c46eb4 100644 --- a/src/OpenSHC/AI/AICState/buyGoods.cpp +++ b/src/OpenSHC/AI/AICState/buyGoods.cpp @@ -2,6 +2,7 @@ #include "OpenSHC/Game/GameStateStructures.func.hpp" #include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/Globals/DAT_BuildingsState.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -12,10 +13,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CC000 BOOLEnum AICState::buyGoods(int playerID, ResourceType resourceType, int amount) { - int const cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + int cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( playerID, resourceType, amount); if (MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::processResourceGain, DAT_BuildingsState::ptr)( - playerID, resourceType, amount)) { + playerID, resourceType, amount) + != FALSE) { DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] -= cost; DAT_GameState::instance.playerDataArray[playerID].marketGold -= cost; MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( @@ -24,6 +26,5 @@ namespace AI { } return FALSE; } - } } diff --git a/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp new file mode 100644 index 000000000..6fde84ca6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D39B0 + void AICState::buyRequiredGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + for (int i = 0; i < 25; i++) { + int resourceType = DAT_SkirmishDefinedData::instance.ResourceAcquisitionPreferenceOrder[i]; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && i < 12) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] < 20) + resourceType = Game::Resources::RT_WOOD; + + int amount = DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType]; + if (amount == 0) + continue; + if ((i > 3 + || DAT_GameState::instance.playerDataArray[playerID].popularity + >= this->aics[aicIndex].criticalPopularity) + && DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + && DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + playerID, resourceType, amount)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::requestGoods, this)( + playerID, (ResourceType)resourceType, amount * 2); + continue; + } + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyGoods, this)( + playerID, (ResourceType)resourceType, amount) + != FALSE) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType] = 0; + return; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp new file mode 100644 index 000000000..2b8922be6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD250 + BOOLEnum AICState::canNavigateUnitsFromTileToTargetTile(int tribeID, int tile) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + int fromArea = (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile]; + return MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)( + owner, fromArea, (short)DAT_TileMapState::instance.PathConnectionLayer[tile], 0) + != 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp new file mode 100644 index 000000000..ffc6b981f --- /dev/null +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDB20 + int AICState::chooseRandomBuildingIDFromPlayersArrayOfBuildings(int playerID) + { + int chosenBuildingID = 0; + int chosenPriority = 46; + int buildingCount = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + int rng = SEC_RNG::instance.currentNumber2 & 7; + for (int i = 0; i < buildingCount; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) + continue; + + int buildingType = DAT_BuildingsState::instance.buildings[buildingID].buildingType; + for (int j = 0; j < chosenPriority; j++) { + bool isMatch; + if (rng <= 1) + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]; + else if (rng <= 3) + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]; + else + isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]; + + if (isMatch) { + chosenBuildingID = buildingID; + chosenPriority = j; + } + } + } + return chosenBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp new file mode 100644 index 000000000..8e7e23178 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp @@ -0,0 +1,39 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF870 + void AICState::clearOutdatedAITribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + if (i == 8) + continue; + + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j] = 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType] = 0; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp new file mode 100644 index 000000000..9eb65d0be --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAC70 + void AICState::clearPlayerDataInformationChunk() + { + for (int i = 0; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1 + || DAT_GameSynchronyState::instance.currentAIArray[i] == 0) + MACRO_CALL(OpenSHC::OS_Func::_memset)(&DAT_GameState::instance.playerDataArray[i].aiFarmIndex, 0, 3748); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp new file mode 100644 index 000000000..09533eb02 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE790 + void AICState::clearRallyFlagOnAllAIAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp new file mode 100644 index 000000000..83e39cbc1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp @@ -0,0 +1,40 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF7C0 + void AICState::commandFourTribesToMove(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 3; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[186 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 186]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp new file mode 100644 index 000000000..4a8f8f26d --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2EC0 + void AICState::computeAttackPossibilities(int playerID) + { + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIInfoLayer); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackPrimeInfo, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeTargetedBuildingTilesArray, + DAT_TroopValueState::ptr)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeAttackInfo, DAT_TroopValueState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::computeSiegeSpotScores, DAT_TroopValueState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setScale3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setTown2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setGate2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setWide3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setMoat3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp new file mode 100644 index 000000000..d158bc23f --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 + void AICState::computeEnemyKeepApproachTile(int playerID) + { + int keepX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; + int keepY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + this->aiBorderTilesIndex = 0; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 8000, '\0', this->aiBorderTiles); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + int tile = this->aiBorderTiles[i].tile; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(keepX, keepY, + tile + - DAT_ViewportRenderState::instance + .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] + .addXgetTile, + DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]); + this->aiBorderTiles[i].distance = DAT_TileMapState::instance.unitDeathHeatMap[tile] + + DAT_DirectionAlgorithmState::instance.distanceHigh; + } + + int shortestDistanceTile = 0; + int shortestDistance = 100000; + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + if (this->aiBorderTiles[i].distance < shortestDistance) { + shortestDistanceTile = this->aiBorderTiles[i].tile; + shortestDistance = this->aiBorderTiles[i].distance; + } + } + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = 0; + if (shortestDistanceTile <= 0) + return; + + short y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY = y; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = shortestDistanceTile; + int x = shortestDistanceTile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y + = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY; + + if ((short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX] + != 0) + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)( + (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x, + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y, -1, -1, 2000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::storeDestinationOptionsUnk, + DAT_PathFindingState::ptr)(playerID - 1); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeNervousness.cpp b/src/OpenSHC/AI/AICState/computeNervousness.cpp new file mode 100644 index 000000000..2d92d4bca --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeNervousness.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3780 + void AICState::computeNervousness(int playerID) + { + int troopValueNearKeep + = DAT_TroopValueState::instance.attackInfo.playerTotalTroopValueOfTroopsNearKeep[playerID]; + int enemyTroopValue = DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + + int margin; + if (enemyTroopValue < 50) + margin = -20; + else if (enemyTroopValue < 100) + margin = 0; + else if (enemyTroopValue < 200) + margin = 20; + else if (enemyTroopValue < 400) + margin = 100; + else if (enemyTroopValue < 600) + margin = 200; + else if (enemyTroopValue < 800) + margin = 300; + else + margin = 500; + + if (troopValueNearKeep > enemyTroopValue + margin) { + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 0; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID] + .totalEnemyTroopValueByPlayerID[DAT_GameSynchronyState::instance.currentPlayerSlotID] + > troopValueNearKeep) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous1BikFromPlayer, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playRequestHelpBikFromPlayer, this)(playerID); + } + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp new file mode 100644 index 000000000..4cad5f5ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp @@ -0,0 +1,36 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFBC0 + int AICState::countSiegeEngineUnits(int playerID) + { + int count = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TREBUCHET + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TOWER + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_BATTERINGRAM + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + count++; + } + return count; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp new file mode 100644 index 000000000..4395e208d --- /dev/null +++ b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp @@ -0,0 +1,26 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC910 + int AICState::createTribeForUnitType(int playerID, int unitSelectionTypeIndex) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp new file mode 100644 index 000000000..48f809905 --- /dev/null +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -0,0 +1,154 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1A00 + void AICState::decideOnTribeAttackLocation(uint tribeID) + { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) != FALSE) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + return; + } + + int targetPlayer = DAT_TribesState::instance.tribes[tribeID].targetPlayer_outpost; + if (DAT_TribesState::instance.tribes[tribeID].randomNumber == 0) { + DAT_TribesState::instance.tribes[tribeID].randomNumber = SEC_RNG::instance.currentNumber2; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + } + + DAT_TribesState::instance.tribes[tribeID].counter++; + if (DAT_TribesState::instance.tribes[tribeID].counter + == DAT_TribesState::instance.tribes[tribeID].randomNumber % 6 + 7) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + byte moatWasHit = 0; + int x = 0; + int y = 0; + int unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::getUnitIDForIndexInTribe, DAT_TribesState::ptr)(tribeID, 0); + if (unitID != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::pathFindingToAttackCastleIncludingMoat, + DAT_PathFindingState::ptr)(DAT_TribesState::instance.tribes[tribeID].owner, targetPlayer, + DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y, &moatWasHit, &x, + &y)) { + if (moatWasHit != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, (UnitInstructionType)35, + DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile + x, 0, 0); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_FILL_MOAT, x, y, 1000); + return; + } + } + + if (DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 182 + && DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 185) { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, FALSE) != FALSE) + return; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + return; + } + + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + if (buildingID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = buildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + return; + } + + int destinationX = DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + int destinationY = DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, + DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile + destinationX) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + return; + } + + int buildingID = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + int destinationTile; + if (buildingID == 0) { + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } else if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, + DAT_PathFindingState::ptr)(tribeID, buildingID, 1, + (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile], + targetPlayer); + destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; + if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } + + if (destinationTile == 0) + return; + + int destinationY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[destinationTile]; + int destinationX + = destinationTile - DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)( + tribeID, destinationTile) + == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, targetPlayer); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp new file mode 100644 index 000000000..a8c02b8b2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp @@ -0,0 +1,97 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Buildings/BuildingLogicalState.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0280 + void AICState::destroyAnyBuildingInCategory(int playerID, AINervousDestroyBuildingCategory buildingCategory) + { + for (int buildingID = 1; buildingID < DAT_BuildingsState::instance.maxBuildingsCount; buildingID++) { + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == 0) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == BLS_REMOVE) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].owner != playerID) + continue; + + bool matches = false; + if (buildingCategory == AINDBC_FEAR_FACTOR_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CESSPIT + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BURNINGSTAKE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GIBBET + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNGEON + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STRETCHINGRACK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_RACKFLOGGING + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHOPPINGBLOCK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNKINGSTOOL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DOGCAGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STATUE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_SHRINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DANCINGBEAR + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POND + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GALLOWS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STOCKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WITCHHOIST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MAYPOLE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GARDEN; + else if (buildingCategory == AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_FLETCHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BLACKSMITH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POLETURNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ARMOURER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TANNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_INN + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APOTHECARY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHAPEL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHURCH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CATHEDRAL; + else if (buildingCategory == AINDBC_GATEHOUSES_TOWERS_GUILDS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSELARGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSESMALL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DRAWBRIDGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER1 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER2 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER3 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER4 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER5 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ENGINEERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TUNNELERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OILSMELTER; + else if (buildingCategory == AINDBC_BARRACKS_MERCPOST_STABLES) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MERCENARYPOST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BARRACKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STABLES; + else if (buildingCategory == AINDBC_WATER_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WELL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WATERPOT; + else if (buildingCategory == AINDBC_RESOURCE_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OXTETHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_IRONMINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_PITCHRIG + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BAKERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BREWERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_QUARRY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MILL; + else if (buildingCategory == AINDBC_FARMS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WHEATFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_HOPFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APPLEFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DAIRYFARM; + + if (!matches) + continue; + + MACRO_CALL_MEMBER(BuildingsState_Func::giveBackResourceForDestroyedBuilding, DAT_BuildingsState::ptr)( + buildingID, playerID, 50); + MACRO_CALL_MEMBER(BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp new file mode 100644 index 000000000..e041584c2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp @@ -0,0 +1,44 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB940 + undefined4 AICState::destroyBuildingIfNoWorker(int buildingID) + { + short countdown = DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue; + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (countdown <= 0) + return 0; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[owner] != -1) + return 0; + if (DAT_GameSynchronyState::instance.currentAIArray[owner] == 0) + return 0; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return 0; + + if (DAT_BuildingsState::instance.buildings[buildingID].workers[0] != 0) { + if (DAT_GameState::instance.mapAndTime.monthTicks == owner) + DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue = countdown - 1; + return 0; + } + if (countdown < 20) + return 0; + + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].x, DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, owner, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + return 1; + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyHouse.cpp b/src/OpenSHC/AI/AICState/destroyHouse.cpp new file mode 100644 index 000000000..acaa30e30 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyHouse.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB810 + BOOLEnum AICState::destroyHouse(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation + > DAT_GameState::instance.playerDataArray[playerID].populationCap) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation <= 8) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk <= 20) + return FALSE; + + int hovelID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_HOVEL); + if (hovelID == 0) + return FALSE; + + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(hovelID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(hovelID); + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp new file mode 100644 index 000000000..6f54dca38 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D38C0 + BOOLEnum AICState::determineAIPlayerAttackRequestResponse(int playerID, undefined4 param_2, int requestedByPlayerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillAttackBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp new file mode 100644 index 000000000..df8442f09 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp @@ -0,0 +1,36 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3850 + BOOLEnum AICState::determineAIPlayerHelp(int playerID, int requestedByPlayerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillHelpBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/digNearestMoat.cpp b/src/OpenSHC/AI/AICState/digNearestMoat.cpp new file mode 100644 index 000000000..a8f101b79 --- /dev/null +++ b/src/OpenSHC/AI/AICState/digNearestMoat.cpp @@ -0,0 +1,54 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD160 + void AICState::digNearestMoat(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10]) + return; + if (DAT_TribesState::instance.tribes[tribeID].size <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].percentageSomething > 80) + return; + + if (MACRO_CALL_MEMBER(Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)( + playerID) + <= 0) { + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + return; + } + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + int tile = MACRO_CALL_MEMBER(Map::TileMapState_Func::findNearestFriendlyMoatTileForDigging, + DAT_TileMapState::ptr)(playerID, targetUnitID, 1); + if (tile <= -1) + return; + + MACRO_CALL_MEMBER(Map::TileMapState_Func::setXYBasedOnMoatID, DAT_TileMapState::ptr)( + tile, 1, DAT_UnitsState::instance.units[targetUnitID].x, DAT_UnitsState::instance.units[targetUnitID].y); + if (MACRO_CALL_MEMBER(Map::Units::TribesState_Func::updateTribeRallyFlags, DAT_TribesState::ptr)(tribeID) != 0) + return; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + Map::Units::UIT_DIG_MOAT, DAT_TileMapState::instance.ALG_MoatXResult, + DAT_TileMapState::instance.ALG_MoatYResult, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + } +} +} diff --git a/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp new file mode 100644 index 000000000..19151eaa5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFB20 + void AICState::disbandAIPlayerSiegeUnits(int playerID) + { + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TOWER + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_BATTERINGRAM + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_SHIELD + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + DAT_UnitsState::instance.units[unitID].tribeID, OpenSHC::Map::Units::UIT_EXIT_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + DAT_UnitsState::instance.units[unitID].state.generic = OpenSHC::Map::Units::States::US_DISAPPEAR; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp new file mode 100644 index 000000000..b854d8312 --- /dev/null +++ b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE930 + void AICState::findAttackTribePositionAndPathToDefenses(int playerID) + { + int x = 0; + int y = 0; + for (int i = 0; i < 11; i++) { + int targetUnitID = 0; + for (int j = 0; j < DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[j + DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType]) + continue; + + targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + x = DAT_UnitsState::instance.units[targetUnitID].x; + y = DAT_UnitsState::instance.units[targetUnitID].y; + break; + } + if (targetUnitID != 0) + break; + } + + DAT_GameState::instance.playerDataArray[playerID].someTile = 0; + if (x == 0) + return; + + int tile = MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::tracePathToFriendlyDefensesAndReturnTileUnk, + DAT_PathFindingState::ptr)(playerID, x, y); + if (tile <= 0) + return; + + short tileY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + DAT_GameState::instance.playerDataArray[playerID].someTile = tile; + DAT_GameState::instance.playerDataArray[playerID].someY = tileY; + DAT_GameState::instance.playerDataArray[playerID].someX + = tile - DAT_ViewportRenderState::instance.translationMatrix[tileY].addXgetTile; + } +} +} diff --git a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp new file mode 100644 index 000000000..95a84c59f --- /dev/null +++ b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp @@ -0,0 +1,52 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC420 + void AICState::generateSiegeCreationInformation(int playerID, int buildingID, int unitID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + if (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SOLITARY) + return; + + int siegeIndex; + if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TREBUCHET) + siegeIndex = 2; + else if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + siegeIndex = 3; + else + return; + + int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + int i; + for (i = 0; i < locationCount; i++) { + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i] > 0 + && DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID + == buildingID) + break; + } + if (i >= locationCount) + return; + + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID = unitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[unitID].uid; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].buildingID = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp index b76f6d0d0..de9383a30 100644 --- a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp +++ b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp @@ -1,5 +1,4 @@ -#include "../AICState.hpp" - +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/AI/AITypeA.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -9,14 +8,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB120 int AICState::getAIBuildInterval(int playerID) - { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; - if (aiType == AITA_NULL) { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) return 0; - } - - return this->aics[(aiType - 1)].buildInterval; + return this->aics[aiType - 1].buildInterval; } } } diff --git a/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp new file mode 100644 index 000000000..c4ad3be31 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D12A0 + int AICState::getCurrentDesiredAttackRaidUnitCount(int aiType, int playerID) + { + if (DAT_GameCore::instance.gameMode_2 == Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == Game::TT_EXTREME) + return (DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase) + * 4 / 3; + return DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp new file mode 100644 index 000000000..fd1ac266c --- /dev/null +++ b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp @@ -0,0 +1,53 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCBE0 + int AICState::getDefensiveTribeForUnit(int playerID, int unitID) + { + int index; + for (index = 0; index < 20; index++) { + if (DAT_UnitsState::instance.units[unitID].unitType + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index] + || DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index]) + break; + } + + int tribeIndex; + if (index < 1) + tribeIndex = 180; + else if (index < 2) + tribeIndex = 181; + else if (index < 7) + tribeIndex = 182; + else if (index < 12) + tribeIndex = 183; + else if (index < 15) + tribeIndex = 184; + else if (index < 20) + tribeIndex = 185; + else + return 0; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp new file mode 100644 index 000000000..5ab4f3163 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB150 + int AICState::getResourceRebuildDelay(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return this->aics[aiType - 1].resourceRebuildDelay; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp new file mode 100644 index 000000000..cea616412 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCAF0 + int AICState::getSmallestPatrolTribe(int playerID, int tribeCount) + { + int selectedTribeID = 0; + int selectedIndex = 0; + int lowestSize = 1000; + + for (int i = 0; i < tribeCount; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) { + selectedTribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedIndex = i; + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].size < lowestSize) { + lowestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedIndex = i; + } + } + + if (selectedTribeID == 0) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedIndex + 170] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedIndex + 170] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp new file mode 100644 index 000000000..b997e2dee --- /dev/null +++ b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDBC0 + int AICState::getTargetableBuildingForPlayerID(int playerID, int param_2) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return 0; + int outerPatrolGroupsCount = this->aics[aiType - 1].OuterPatrolGroupsCount; + if (outerPatrolGroupsCount <= 0) + return 0; + int tracker = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + if (tracker <= 0) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].someCounter2++; + if (DAT_GameState::instance.playerDataArray[playerID].someCounter2 >= tracker) + DAT_GameState::instance.playerDataArray[playerID].someCounter2 = 0; + + int index = (tracker / outerPatrolGroupsCount * param_2 + + DAT_GameState::instance.playerDataArray[playerID].someCounter2) + % tracker; + for (int i = 0; i < tracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + index++; + if (index >= tracker) + index = 0; + if (buildingID == 0) + continue; + + for (int j = 0; j < 8; j++) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + return buildingID; + } + } + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp new file mode 100644 index 000000000..3a4accdb0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp @@ -0,0 +1,57 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC250 + UnitType AICState::getUnitTypeForUnitBehaviourType(int playerID, AIUnitBehaviourType unitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return (UnitType)0; + int aicIndex = aiType - 1; + + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGINEERING) + return OpenSHC::Map::Units::UT_E_ENGINEER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_DIGGING) + return (UnitType)this->aics[aicIndex].AttDiggingUnit; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ASSASSINATION) + return OpenSHC::Map::Units::UT_A_ASSASSIN; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ATTUNIT2) + return (UnitType)this->aics[aicIndex].AttUnit2; + if (unitBehaviourType == OpenSHC::AI::AIUBT_LADDERING) + return OpenSHC::Map::Units::UT_E_LADDER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_TUNNELING) + return OpenSHC::Map::Units::UT_TUNNELER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_PATROLLING) + return (UnitType)this->aics[aicIndex].AttUnitPatrol; + if (unitBehaviourType == OpenSHC::AI::AIUBT_BACKUPING) + return (UnitType)this->aics[aicIndex].AttUnitBackup; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGAGING) + return (UnitType)this->aics[aicIndex].AttUnitEngage; + if (unitBehaviourType == OpenSHC::AI::AIUBT_SIEGEDEFENSE) + return (UnitType)this->aics[aicIndex].AttUnitSiegeDef; + if (unitBehaviourType != OpenSHC::AI::AIUBT_ATTUNITMAIN) + return (UnitType)this->aics[aicIndex].AttUnitMain1; + + if ((&this->aics[aicIndex] + .AttUnitMain1)[DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex >= 4) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + + int choice = DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex; + UnitType unitType = (UnitType)(&this->aics[aicIndex].AttUnitMain1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = choice + 1; + return unitType; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp new file mode 100644 index 000000000..00d41635a --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp @@ -0,0 +1,50 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEB50 + void AICState::giveMoveCommandToAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeType == 18 || tribeType == 190 || tribeType == 15) + continue; + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = (char)(SEC_RNG::instance.currentNumber2 & 1); + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp new file mode 100644 index 000000000..30db96be3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp @@ -0,0 +1,71 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD8F0 + void AICState::giveMoveCommandToSortieUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + int aicIndex = aiType - 1; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[167]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 - 1) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::syncBehaviourType7UnitTribes, this)(playerID); + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { + if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitMeleeMin) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + int unitID = DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitID; + if (unitID != 0 + && DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitUID + == DAT_UnitsState::instance.units[unitID].uid + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + (short) + DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + 0) + != 0) { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[unitID].x, + DAT_UnitsState::instance.units[unitID].y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp new file mode 100644 index 000000000..c094f57f1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF920 + undefined4 AICState::giveSomeRaidCommand(int tribeIndex, int tribeType) + { + int owner = DAT_TribesState::instance.tribes[tribeIndex].owner; + int targetBuildingID = DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID; + if (tribeType == 186 || tribeType == 18 || tribeType == 14 || tribeType == 13) + return 0; + + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0) + return 1; + + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = 0; + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[owner].attackedPlayerID); + if (buildingID == 0) + return 0; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::queueDisbandAndAttackCommand4Params, + DAT_UnitsState::ptr)(tribeIndex, 9, buildingID, DAT_BuildingsState::instance.buildings[buildingID].uid); + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + DAT_TribesState::instance.tribes[tribeIndex].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = buildingID; + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp new file mode 100644 index 000000000..b009cbc8a --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFFD0 + BOOLEnum AICState::hasNoTroopsOrAllAreDiggers(int playerID) + { + int totalTroops = DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops; + int halfOfDiggingTroops = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState == 6) { + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 16) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } else { + halfOfDiggingTroops = DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops / 2; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 12) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } + + if (totalTroops <= 1) + return TRUE; + if (totalTroops <= 4 && halfOfDiggingTroops > totalTroops * 2) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp new file mode 100644 index 000000000..96e8e3c36 --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp @@ -0,0 +1,30 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D00A0 + BOOLEnum AICState::hasNotEnoughSupplies(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].marketplace.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_MARKETPLACE].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].granary.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_GRANARY].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount <= 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].popularity <= 25 + && DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp new file mode 100644 index 000000000..95b2d1aeb --- /dev/null +++ b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2830 + void AICState::ifHasTribe0SendUnitsToKeep(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0]; + if (tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0]) + return; + + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp new file mode 100644 index 000000000..22d967f1f --- /dev/null +++ b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp @@ -0,0 +1,69 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD7B0 + void AICState::instructTribe166ToMove(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 0) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01--; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 50) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 = 50; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + BOOLEnum keepEnclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[166]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 - 5) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitsToTribe166, this)(playerID); + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && keepEnclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitRangedMin) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].someXPosition <= 0 + || DAT_GameState::instance.playerDataArray[playerID].someYPosition <= 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + return; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)( + tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_GameState::instance.playerDataArray[playerID].someXPosition, + DAT_GameState::instance.playerDataArray[playerID].someYPosition, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp new file mode 100644 index 000000000..d4224026c --- /dev/null +++ b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0520 + BOOLEnum AICState::isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade( + int playerID, ResourceType resourceType, int buffer) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return TRUE; + if (resourceType == Game::Resources::RT_GOLD) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[resourceType] + < this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].minimumGoodsRequiredAfterTrade + + buffer) + return FALSE; + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp new file mode 100644 index 000000000..243d4828c --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3960 + void AICState::makeOutpostTribesAttack() + { + for (int i = 0; i < 1000; i++) { + int tribeID = this->tribeIDArray[i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid != this->tribeUIDArray[i]) + continue; + + MACRO_CALL_MEMBER(AICState_Func::decideOnTribeAttackLocation, this)(tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp new file mode 100644 index 000000000..b9d4faf2f --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEA50 + void AICState::makeUnitsGoDefensiveAndBackToSomeLocation(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].someTile == 0) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + uint result = MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_GameState::instance.playerDataArray[playerID].someX, + DAT_GameState::instance.playerDataArray[playerID].someY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + if (result == 0) + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::removeAllTribeUnits, DAT_TribesState::ptr)( + tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp new file mode 100644 index 000000000..41ac2d515 --- /dev/null +++ b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp @@ -0,0 +1,19 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECAB0 + BOOLEnum AICState::mapperIsWoodCuttersHutWhileNoWoodAvailable( + undefined4 playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_WOODSMAN + && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp new file mode 100644 index 000000000..ee8e3ad57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE830 + void AICState::moveAttackTribesToLocations(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp new file mode 100644 index 000000000..04aa9c19d --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp @@ -0,0 +1,71 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2F80 + void AICState::moveAttackingUnitsToNextRallyPoint(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 2; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[190 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 190]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::allUnitsReachedTheirDestination, + DAT_TribesState::ptr)(tribeID) + == FALSE) + continue; + + DAT_TribesState::instance.tribes[tribeID].unknownAttackRelatedUpdateCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addRallyPointForTribe, this)(tribeID); + + short rallyPointCount = DAT_TribesState::instance.tribes[tribeID].rallyPointCount; + int rallyPointIndex = DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + 1; + if (rallyPointIndex >= rallyPointCount) + rallyPointIndex = 0; + + int x = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][0]; + int y = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][1]; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = rallyPointIndex; + + if (rallyPointCount <= 0) + continue; + + if (rallyPointCount <= 1) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= 5) + continue; + } else { + if (DAT_UnitsState::instance.units[targetUnitID].closestEnemyMicroDistance <= 96) + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp new file mode 100644 index 000000000..a7af36538 --- /dev/null +++ b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CED90 + BOOLEnum AICState::percentageNonMovingTribesGTEAICSpecified(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttMovingTribesThreshold; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp new file mode 100644 index 000000000..4db7857fd --- /dev/null +++ b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp @@ -0,0 +1,226 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using namespace OpenSHC::Game::Resources; + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBA50 + void AICState::planToBuyWhenLowOnResourceAndSnoozeBuildings(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int allowFletchers = 1; + int allowIronMines = 1; + int allowFarms = 1; + int allowPitchRigs = 1; + int allowWoodcutters = 1; + int allowPoleturners = 1; + int allowQuarries = 1; + int allowBlacksmiths = 1; + int allowArmourers = 1; + int allowTanners = 1; + int allowBakeries = 1; + int allowBreweries = 1; + int allowInns = 1; + int allowWells = 1; + + int buildingBurning = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::playerHasBurningBuilding, DAT_BuildingsState::ptr)(playerID); + int wood = DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_WOOD]; + + if (wood <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_IRON] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_FLOUR] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_HOPS] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_ALE] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk = 0; + } + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter++; + + if (DAT_GameState::instance.playerDataArray[playerID].countFletchersPoleturners <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countArmorersAndBlacksmiths <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBakers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBrewers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + + int population = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (population < 10 + && (DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + || DAT_GameState::instance.playerDataArray[playerID].countWoodcutters > 2)) { + allowQuarries = 0; + } + + if (wood >= 20 && population < 10 && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood < 8) { + allowWoodcutters = 0; + } + + if (buildingBurning == 0 + && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers + >= DAT_GameState::instance.playerDataArray[playerID].countFarms + && DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_GOLD] < 500) { + allowWells = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].someResourceCounter > 36) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36) { + allowBakeries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72) { + allowBreweries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk > 72) { + allowInns = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + allowFletchers = 1; + allowIronMines = 0; + allowQuarries = 0; + allowFarms = 0; + allowPitchRigs = 0; + allowWoodcutters = 0; + allowPoleturners = 1; + allowBlacksmiths = 1; + allowArmourers = 1; + allowTanners = 1; + allowBakeries = 0; + allowBreweries = 0; + allowInns = 0; + } + + if (allowWells) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = true; + } + + if (allowQuarries) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = true; + } + + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WOODCUTTERSHUT] = !allowWoodcutters; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_PITCHRIG] = !allowPitchRigs; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_POLETURNER] = !allowPoleturners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_ARMOURER] = !allowArmourers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_TANNER] = !allowTanners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BAKERY] = !allowBakeries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BREWERY] = !allowBreweries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_INN] = !allowInns; + + if (allowFarms) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = true; + } + + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] = 5; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] = 2; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp new file mode 100644 index 000000000..c2f18af59 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/INT_ARRAY_00eb0af0.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0830 + void AICState::playAnger1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + DWORD now = timeGetTime(); + if (INT_ARRAY_00eb0af0::instance[playerID] != 0 && now - INT_ARRAY_00eb0af0::instance[playerID] < 600000) { + return; + } + + INT_ARRAY_00eb0af0::instance[playerID] = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 5); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..df0b308ad --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0C00 + void AICState::playAnger2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 = 96; + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 6); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp new file mode 100644 index 000000000..64249dc9c --- /dev/null +++ b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0FC0 + void AICState::playFriendlyVictoryBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 10); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp new file mode 100644 index 000000000..ca55ee528 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B70 + void AICState::playGoodsNotSentBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 28); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..acb064856 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0A20 + void AICState::playGoodsRequestBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 14); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..7b807ddfc --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0BB0 + void AICState::playGoodsSentBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 29); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp new file mode 100644 index 000000000..639186e16 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp @@ -0,0 +1,22 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0F90 + void AICState::playJokeBikFromAIToHuman(int playerID, int aiType) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType, 20); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp new file mode 100644 index 000000000..0bdd24ea6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0CF0 + void AICState::playNervous1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 = 48; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 8); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp new file mode 100644 index 000000000..403a03302 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B10 + void AICState::playNervous2BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown != 0) { + return; + } + + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int currentPlayerID = DAT_GameSynchronyState::instance.currentPlayerSlotID; + DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown = 48; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[currentPlayerID]) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType - 1, 9); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp new file mode 100644 index 000000000..07b38053e --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1050 + void AICState::playNoHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 31); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp new file mode 100644 index 000000000..a652216ca --- /dev/null +++ b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp @@ -0,0 +1,73 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D08B0 + void AICState::playPlayerKilledBiksFromKilledAndKillerPlayer(int killerPlayerID, int killedPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(killerPlayerID) + == 0) { + return; + } + + if (killedPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 12); + return; + } + + if (killerPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.playerDataArray[i].aiType == AITA_NULL) { + continue; + } + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + i, DAT_GameState::instance.playerDataArray[i].aiType - 1, 17); + } + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID] + == DAT_GameState::instance.mapAndTime.playerTeams[killedPlayerID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 16); + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[killerPlayerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 18); + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 13); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp new file mode 100644 index 000000000..5f1cee729 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0D40 + void AICState::playRequestHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 = 48; + + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 19); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp new file mode 100644 index 000000000..f49839574 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D07C0 + void AICState::playTauntToAttackedPlayerBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int bik = 0; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + bik = 23; + } + + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + if (bik == 0) { + bik = (SEC_RNG::instance.currentNumber2 & 3) + 1; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, bik); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp new file mode 100644 index 000000000..429518496 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1000 + void AICState::playTeamWinningBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 30); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..823e435e9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/DWORD_00eb0b18.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0A80 + void AICState::playThanksBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay = this->aics[aicIndex].aiRequestDelay; + + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + DWORD now = timeGetTime(); + if (DWORD_00eb0b18::instance != 0 && now - DWORD_00eb0b18::instance < 1200000) { + return; + } + + DWORD_00eb0b18::instance = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 15); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..0d11e93d4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0C70 + void AICState::playVictory2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[targetPlayerID].aiType == AITA_NULL) { + return; + } + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(targetPlayerID) + == 0) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 = 48; + if (playerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + targetPlayerID, DAT_GameState::instance.playerDataArray[targetPlayerID].aiType - 1, 11); + } +} +} diff --git a/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp new file mode 100644 index 000000000..302c38a63 --- /dev/null +++ b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp @@ -0,0 +1,68 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeIndex.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEEA0 + BOOLEnum AICState::processAttForceRallyPercentage(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_07_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_10_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_05_01) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return (BOOLEnum)(notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttForceRallyPercentage); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp new file mode 100644 index 000000000..4b0108c3d --- /dev/null +++ b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp @@ -0,0 +1,138 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIUnitBehaviourType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC070 + AIUnitBehaviourType AICState::randomlySelectAttackUnitTypeToRecruit(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int aicIndex = aiType - 1; + int requiredCount = 0; + + // clear out flags + for (int i = 0; i < 11; i++) { + DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required = 0; + } + + int engineersCount = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (engineersCount > (int)this->aics[aicIndex].AttMaxEngineers) { + engineersCount = this->aics[aicIndex].AttMaxEngineers; + } + if (this->aics[aicIndex].AttMaxEngineers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops < engineersCount) { + requiredCount = 1; + DAT_SkirmishDefinedData::instance.attackUnitRequired[0].required = 1; + } + + if (this->aics[aicIndex].AttDiggingUnitMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops + < (int)this->aics[aicIndex].AttDiggingUnitMax + && DAT_GameState::instance.playerDataArray[attackedPlayerID].moatsOwned > 5) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[1].required = 1; + } + + if (this->aics[aicIndex].AttMaxAssassins != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + < (int)this->aics[aicIndex].AttMaxAssassins) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[2].required = 1; + } + + if (this->aics[aicIndex].AttUnit2Max != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + < (int)this->aics[aicIndex].AttUnit2Max) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[3].required = 1; + } + + if (this->aics[aicIndex].AttMaxLaddermen != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalLaddermenTroops + < (int)this->aics[aicIndex].AttMaxLaddermen) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[4].required = 1; + } + + if (this->aics[aicIndex].AttMaxTunnelers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalTunnelerTroops + < (int)this->aics[aicIndex].AttMaxTunnelers) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[5].required = 1; + } + + if (this->aics[aicIndex].AttUnitPatrolMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops + < (int)this->aics[aicIndex].AttUnitPatrolMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[6].required = 1; + } + + if (this->aics[aicIndex].AttUnitBackupMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops + < (int)this->aics[aicIndex].AttUnitBackupMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[7].required = 1; + } + + if (this->aics[aicIndex].AttUnitEngageMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + < (int)this->aics[aicIndex].AttUnitEngageMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[8].required = 1; + } + + if (this->aics[aicIndex].AttUnitSiegeDefMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + < (int)this->aics[aicIndex].AttUnitSiegeDefMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[9].required = 1; + } + + if (this->aics[aicIndex].AttMaxDefault != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops + < (int)this->aics[aicIndex].AttMaxDefault) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[10].required = 1; + } + + if (requiredCount <= 0) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int selection = (int)SEC_RNG::instance.currentNumber2 % requiredCount; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + for (int i = 0; i < 11; i++) { + if (DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required == 0) { + continue; + } + if (selection == 0) { + return (AIUnitBehaviourType)DAT_SkirmishDefinedData::instance.attackUnitRequired[i].unitBehaviourType; + } + selection--; + } + + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp new file mode 100644 index 000000000..5fae78c3f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE040 + void AICState::recomputeAIPathCostToAttackedKeep(int playerID, int useBorderDistance) + { + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int keepArea = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[playerID].campground.tileEntry]; + + int borderDistance; + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 120) { + borderDistance = 98; + } else { + borderDistance + = DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 90 ? 70 + : 50; + } + if (useBorderDistance == 0) { + borderDistance = 0; + } + + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = attackedPlayerID; + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 1, 1); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 0, 1); + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(110, borderDistance, keepArea, playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp new file mode 100644 index 000000000..2478afb94 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp @@ -0,0 +1,31 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE1A0 + void AICState::recomputeAIZonerLayer() + { + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, DAT_PathFindingState::ptr)(1, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, 1); + DAT_GameState::instance.playerDataArray[1].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp new file mode 100644 index 000000000..905549bec --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/AttackInfoSubElement.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::AttackInfoSubElement; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE110 + void AICState::recomputeAttackAIZone() + { + for (int playerID = 1; playerID < 9; playerID++) { + int fullID = DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = playerID; + if (fullID == -1 + && DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0 + // fixme: this comparison looks dumb since fullID is already known to be the value, so a cmp eax eax + // is generated, but we need it for the 100% match. + && fullID == DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(playerID, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = 1; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp new file mode 100644 index 000000000..ea0aff1d7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4630 + void AICState::recreateEngineersTribe(int playerID) + { + if (0 >= DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops) { + return; + } + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_tribeID != 0) { + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) { + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12] = 0; + } + } + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp new file mode 100644 index 000000000..d72a3a55f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -0,0 +1,162 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/Unit.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Unit; + using OpenSHC::Map::Units::UnitInstructionType; + using OpenSHC::Map::Units::UnitLogicState; + using OpenSHC::Map::Units::UnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD2A0 + void AICState::recruitHarrassingSiegeEngines(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int engineersGuild = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + + if (aiType == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 500) { + return; + } + + int aicIndex = aiType - 1; + int harrassingSiegeEnginesMax = this->aics[aicIndex].HarassingSiegeEnginesMax; + if (harrassingSiegeEnginesMax <= 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout != 0) { + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout -= 1; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harassingSiegeEnginesCountUnk + >= harrassingSiegeEnginesMax) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].unknownHarrassingSiegeRelated >= 20) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + || DAT_TribesState::instance.tribes[tribeID].size < 2) { + // Not enough engineers yet: recruit one at the engineers guild + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0 + || DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 30 + || engineersGuild <= 0 + || DAT_BuildingsState::instance.buildings[engineersGuild].buildingType + != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) { + return; + } + + uint unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + OpenSHC::Map::Units::UT_E_ENGINEER, engineersGuild, playerID, 0); + if (unitID == 0) { + return; + } + + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165]) { + tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + = DAT_TribesState::instance.tribes[tribeID].uid; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, tribeID); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = OpenSHC::AI::AIUBT_HARASS_SIEGE; + } else { + // Enough engineers: pick the next siege engine from the AIC list and place its tent + if (DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex >= 8) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + if ((&this->aics[aicIndex].HarassingSiegeEngine1)[DAT_GameState::instance.playerDataArray[playerID] + .harrassingSiegeEnginesIndex] + == 0) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + + int engineIndex = DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex; + MappersEnum siegeEngineToBuild = (MappersEnum)(&this->aics[aicIndex].HarassingSiegeEngine1)[engineIndex]; + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = engineIndex + 1; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AIVState_Func::findSpotNearEngineersGuild, DAT_AIVState::ptr)(playerID) + == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + + // Place the siege tent + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + DAT_AIVState::instance.buildingApproriateGridXPosition * 5, + DAT_AIVState::instance.buildingAppropriateGridYPosition * 5, siegeEngineToBuild, 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail != FALSE) { + return; + } + + int buildingID = DAT_TileMapState::instance.placedBuildingID; + DAT_BuildingsState::instance.buildings[buildingID].attackWave = 0; + DAT_BuildingsState::instance.buildings[buildingID].unknownSiegeTentRelated01 = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + } + + // Point all harassing siege engines at the currently attacked player + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != OpenSHC::Map::Units::ULS_INVISIBLE + && DAT_UnitsState::instance.units[unitID].dying == 0 + && (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + && DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == OpenSHC::AI::AIUBT_SIEGE_01) { + DAT_UnitsState::instance.units[unitID].siegeTargetPlayerID + = (short)DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + } + } + + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout = 8; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp new file mode 100644 index 000000000..b81a2726e --- /dev/null +++ b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE430 + void AICState::removeOrganismsAndSetMoveDestinationPairs(int playerID) + { + // Clear a tree standing on the target tile + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + // Hardcoded exception: the Sultan keeps a smaller distance than all other AIs + int extraDistance + = DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_SULTAN ? 8 : 20; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 10000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::setMoveDestinationPairs, + DAT_PathFindingState::ptr)(playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].someMinimumDistance + extraDistance); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp new file mode 100644 index 000000000..949062c85 --- /dev/null +++ b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/AI/AIC/AICSpecification.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Text/TextAlignment.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BlendingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_RenderingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AICState; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::AI::AIType; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Text::TextAlignment; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1320 + void AICState::renderDebugDataAiInfo(int x, int y, int width, int height) + { + char text[1000]; + + // One row of 24 pixels per player, starting one row below y + y += 24; + for (int playerID = 1; playerID <= 8; playerID++, y += 24) { + if (DAT_GameState::instance.playerDataArray[playerID].playerDeathRelated != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL + && DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + continue; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + uint color = DAT_RenderingDefinedData::instance + .ColorArray[DAT_BlendingDefinedData::instance.PlayerSlotUnitColor[playerID]]; + + // Player number and name + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumberToScreen2, DAT_TextManagerObject::ptr)( + playerID, x + 6, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderTextToScreen, DAT_TextManagerObject::ptr)( + "-", x + 16, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + DAT_GameSynchronyState::instance.DAT_PlayerNames[playerID], x + 26, y + 1, OpenSHC::Text::TTA_LEFT, + color, 0, 18, FALSE, 0); + + // Strength feeling + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling == OpenSHC::AI::AIST_WEAK) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Weak", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling + == OpenSHC::AI::AIST_DEFAULT) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Normal", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Strong", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Current AIV pause + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + "P:", x + 262, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumber2, DAT_TextManagerObject::ptr)( + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause, x + 264, y, OpenSHC::Text::TTA_LEFT, + color, 0, 18, TRUE, 0); + + // Attack target and coordination level + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Atk:%d W:%d", + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 302, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // fixme: Castle (AIV); the castle ID is passed even for human players, whose format ignores it + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV:n/a", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV: %d", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 382, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Engineers + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "E:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6, + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 + + this->aics[aicIndex].SortieUnitRangedMin); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 452, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Siege troops (attack troops without engineers): attack ticker in white when not attacking, + // otherwise the attack force target + int siegeTroops = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops; + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)( + text, "%d/%d", siegeTroops, DAT_GameState::instance.playerDataArray[playerID].attackTicker); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, 0xffffff, 0, 18, FALSE, 0); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Sg:%d/%d", siegeTroops, + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + + this->aics[aicIndex].AttForceBase); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Raiding/harassing troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Hr:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk, + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)( + aicIndex, playerID)); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 592, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Defensive troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Df:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk, + this->aics[aicIndex].DefWalls); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 657, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/requestGoods.cpp b/src/OpenSHC/AI/AICState/requestGoods.cpp new file mode 100644 index 000000000..2c10772ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/requestGoods.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1F00 + void AICState::requestGoods(int playerID, ResourceType resourceType, int amount) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay != 0) { + return; + } + // Never request raw farm goods or ale + if (resourceType == OpenSHC::Game::Resources::RT_WHEAT || resourceType == OpenSHC::Game::Resources::RT_HOPS + || resourceType == OpenSHC::Game::Resources::RT_ALE) { + return; + } + // Only request food when almost out of it + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + // fixme: NOTE: the original performs the exact same food check a second time, it cannot be removed + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + + // Ask every human teammate that has no pending request from this player + for (int i = 1; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] == -1) { + continue; + } + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] != 0) { + continue; + } + + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] = resourceType; + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray2Unk[playerID] = amount; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playGoodsRequestBikFromPlayerToPlayer, this)(playerID, i); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp new file mode 100644 index 000000000..cacb4823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -0,0 +1,143 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AIC/AITargetChoiceInt.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AITargetChoice; + using OpenSHC::AI::AIC::AITargetChoiceInt; + using OpenSHC::Game::Player::PlayerData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4680 + void AICState::selectAttackTarget(int playerID) + { + int selectedPlayerID = 0; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int fromX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; + int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + int lowestDistance = 10000; + int highestGold = 0; + int lowestPoints = 1000000; + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeNervousness, this)(playerID); + + // Drop a pending request if the asking player's lord is dead + if ((DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + || DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + == 0) { + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = 0; + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) { + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk) + != 0) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; + return; + } + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + + int targetChoice = this->aics[aicIndex].TargetChoice; + if (targetChoice == OpenSHC::AI::AIC::AITC_PLAYER) { + // Closest human player + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[i].currentResources[0xf] >= highestGold) { + highestGold = DAT_GameState::instance.playerDataArray[i].currentResources[0xf]; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[i].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[i].totalTroopValue + + DAT_GameState::instance.playerDataArray[i].currentResources[0xf] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= lowestPoints) { + lowestPoints = points; + selectedPlayerID = i; + } + } + } + + // Fall back to the closest enemy + if (selectedPlayerID == 0) { + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + + if (selectedPlayerID == 0) { + return; + } + } + + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk = selectedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = selectedPlayerID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp new file mode 100644 index 000000000..11008c453 --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDAB0 + int AICState::selectBuildingFromAListOfBuildingTypes(int playerID) + { + // Pick the targetable building whose type comes first in BuildingTargetPrioritySet1 + int selectedBuildingID = 0; + int bestPriority = 46; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) { + continue; + } + for (int j = 0; j < bestPriority; j++) { + if ((short)DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) { + // Found a better match; lowering the bound also ends this loop + bestPriority = j; + selectedBuildingID = buildingID; + } + } + } + + return selectedBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp new file mode 100644 index 000000000..c5845f0bb --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCA0 + int AICState::selectRandomBuildingIDFromListOfBuildingTypes(int playerID, undefined4 param_2) + { + int count = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + if (count <= 0) { + return 0; + } + + int index = SEC_RNG::instance.currentNumber2 % count; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + return DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp new file mode 100644 index 000000000..222bd7854 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp @@ -0,0 +1,105 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategory.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategoryInt.hpp" +#include "OpenSHC/AI/Trading/ResourceAICResourceMappingEntry.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::AI::Trading::AIResourceTradeCategory; + using OpenSHC::AI::Trading::AIResourceTradeCategoryInt; + using OpenSHC::AI::Trading::ResourceAICResourceMappingEntry; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1D60 + void AICState::sellExcessGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int playerResourceBase = playerID * (sizeof(PlayerData) / sizeof(int)); + int nervousActions = DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker; + for (int i = 0; i < 20; i++) { + ResourceType resourceType + = (ResourceType)DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].game; + int category = DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic; + int variance = this->aics[aicIndex].maxResourceVariance; + int resourceOffset = (playerResourceBase + resourceType) * sizeof(int); + + int amount; + if (category == OpenSHC::AI::Trading::AIRTC_WOOD) { + amount = this->aics[aicIndex].maxWood; + } else if (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = this->aics[aicIndex].maxFood; + } else if (category == OpenSHC::AI::Trading::AIRTC_STONE) { + amount = this->aics[aicIndex].maxStone; + } else if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = this->aics[aicIndex].maxEquipment; + } else if (category == OpenSHC::AI::Trading::AIRTC_ALE) { + amount = this->aics[aicIndex].maxBeer; + } else { + amount = this->aics[aicIndex].maxResourceOther; + } + amount += variance; + + if (nervousActions > 0 && DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf] < 500) { + // Nervous and low on gold: keep a quarter of the food and up to 10000 weapons, sell everything else + if (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = amount / 4; + } else if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 10000; + } else { + amount = 0; + } + variance = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].popularity + < this->aics[aicIndex].criticalPopularity) { + // Critical popularity: keep all food, sell all weapons, keep a quarter of everything else + if (category != OpenSHC::AI::Trading::AIRTC_FOOD) { + if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 0; + } else { + amount = amount / 4; + } + } + variance = 0; + } else { + amount += *( + int*)((char*)DAT_GameState::instance.playerDataArray[0].resourcesToAcquireArray + resourceOffset); + } + + // Resources the AIC always sells are kept at zero + for (int j = 0; j < 15; j++) { + if (resourceType == (&this->aics[aicIndex].sellResource01)[j]) { + amount = 0; + variance = 0; + break; + } + } + + if (*(int*)((char*)DAT_GameState::instance.playerDataArray[0].currentResources + resourceOffset) > amount) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellGoods, this)(playerID, resourceType, + ((int*)DAT_GameState::instance.playerDataArray[0] + .currentResources)[playerResourceBase + resourceType] + - amount + variance); + return; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sellGoods.cpp b/src/OpenSHC/AI/AICState/sellGoods.cpp index 5ebd21d63..c830f93ea 100644 --- a/src/OpenSHC/AI/AICState/sellGoods.cpp +++ b/src/OpenSHC/AI/AICState/sellGoods.cpp @@ -23,6 +23,5 @@ namespace AI { MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( playerID, 1, -amount, resourceType); } - } } diff --git a/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp new file mode 100644 index 000000000..8dd7eaed8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF4F0 + void AICState::sendAITribe_0xf_toAttackPatrol(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + // Bug: only tribe 15 is sent, although tribe 16 can also exist if 'AIC: AttUnitPatrolGroupsCount' is 2 + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[15]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[15]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::sendAttackingPatrolTribeToComputedDestination, + DAT_TroopValueState::ptr)(tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp new file mode 100644 index 000000000..abe6c64a1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp @@ -0,0 +1,64 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4220 + void AICState::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed(int playerID) + { + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 6; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + + j]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + j]) { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType, + j); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + + // NOTE: the original sets the stance even when the tribe is invalid (tribeID may be 0) + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp new file mode 100644 index 000000000..5fc7b54b3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp @@ -0,0 +1,62 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D44E0 + void AICState::sendOilMenForOilPickupOrToAIVLocations(int playerID) + { + int oilSmelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1]; + if (count < 1) { + return; + } + if (count == 10) { + count = 9; + } + + // Oil men tribes start at offset 71 in the tribe ID array + for (int i = 1; i <= count; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[70 + i]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[70 + i]) { + continue; + } + + if (DAT_UnitsState::instance.units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .resourceToDeposit + != 0) { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)( + tribeID, OpenSHC::AI::AIVUT_ENGINEER, i - 1); + continue; + } + + // Send for oil duty + if (oilSmelterID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, oilSmelterID, + DAT_BuildingsState::instance.buildings[oilSmelterID].uid, 0); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp new file mode 100644 index 000000000..0110e7fed --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4130 + void AICState::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous(int playerID) + { + for (int i = 0; i < 5; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]) { + continue; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if ((DAT_TileMapState::instance.LogicLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile] + & 0x10000100U) + == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType, j); + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp new file mode 100644 index 000000000..272c3b113 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp @@ -0,0 +1,21 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2870 + void AICState::sendTribeAtOffset1ToCampfire(int playerID) + { + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[1]; + if (_tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[1]) + return; + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(_tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp new file mode 100644 index 000000000..a93f91fa0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2E00 + void AICState::sendTribeToAIVLocationSlot(int tribeID, AIVUnitType aivUnitType, int slotIndex) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + + // Find the slotIndex-th non-empty slot + int tile = 0; + for (int i = 0; i < 10; i++) { + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i] == 0) { + continue; + } + slotIndex--; + if (slotIndex < 0) { + tile = DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i]; + break; + } + } + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + return; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, owner); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp new file mode 100644 index 000000000..82f959aed --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp @@ -0,0 +1,37 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF750 + void AICState::sendTribeToAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xbd]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xbd]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3fb); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp new file mode 100644 index 000000000..6346c66e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp @@ -0,0 +1,109 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D30E0 + undefined4 AICState::sendUnitsToAttackBreachedCastle(int attackingPlayerIndex) + { + int vector = 0; + if (DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiType == OpenSHC::AI::AIT_NULL) { + return 1; + } + + BOOLEnum sentUnitsToAttackLord = FALSE; + int defendingLordID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, + DAT_UnitsState::ptr)(DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID); + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + if (tribeType == 18 || tribeType == 14 || tribeType == 13) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeIDs[tribeType + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeUIDs[j + tribeType]) { + continue; + } + + int targetUnitID = (short)DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + + if (sentUnitsToAttackLord != FALSE || defendingLordID == 0 || tribeType == 186) { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveSomeRaidCommand, this)(tribeID, tribeType) + != 0) { + continue; + } + + if (tribeType == 186) { + vector += 4; + } + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_8; + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + int attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y); + + if (DAT_DirectionAlgorithmState::instance.distanceHigh > 4) { + attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + vector += 1; + } else { + // Send units to attack lord + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[defendingLordID].x, + DAT_UnitsState::instance.units[defendingLordID].y, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0) + == 0) { + return 0; + } + sentUnitsToAttackLord = TRUE; + } + } + } + + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp new file mode 100644 index 000000000..c824f5d75 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD110 + void AICState::sendUnitsToCampfire(int tribeID, int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].campground.id <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + DAT_GameState::instance.playerDataArray[playerID].campground.yEntry, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp new file mode 100644 index 000000000..fcb585017 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD0C0 + void AICState::sendUnitsToKeep(int tribeID, int playerID) + { + int keepID = DAT_GameState::instance.playerDataArray[playerID].keep.id; + if (keepID <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + (short)DAT_BuildingsState::instance.buildings[keepID].x + 3, + (short)DAT_BuildingsState::instance.buildings[keepID].y + 4, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp new file mode 100644 index 000000000..be3532ffb --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp @@ -0,0 +1,79 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE5F0 + void AICState::sendUnitsToPlayerIDsKeepIfPossible(int playerID1, int playerID2) + { + uint campfireX = DAT_GameState::instance.playerDataArray[playerID1].campground.xEntry; + uint campfireY = DAT_GameState::instance.playerDataArray[playerID1].campground.yEntry; + + uint gatehouseX; + uint gatehouseY; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID1, playerID2) + == FALSE) { + uint gatehouseTile = MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findOwnedGatehouse, + DAT_PathFindingState::ptr)(playerID2, campfireX, campfireY); + if (gatehouseTile != 0) { + gatehouseY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[gatehouseTile]; + gatehouseX + = gatehouseTile - DAT_ViewportRenderState::instance.translationMatrix[gatehouseY].addXgetTile; + } else { + DAT_GameState::instance.playerDataArray[playerID1].requestStateUnk = 0; + return; + } + } + + for (int i = 0; i < 11; i++) { + int tribeTypeStart = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID1].aiTribeIDs[tribeTypeStart + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID1].aiTribeUIDs[j + tribeTypeStart]) { + continue; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID1, playerID2) + != FALSE) { + if (tribeTypeStart == 0xbe) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID2); + } else if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::selectionContainsHorses, + DAT_TribesState::ptr)(tribeID) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID2); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID2); + } + } else { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, gatehouseX, gatehouseY, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp new file mode 100644 index 000000000..0296967f4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4340 + void AICState::sendWallPatrolUnitTribesToAIVLocations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 3; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType]; + int groups = count; + if (count > this->aics[aicIndex].defWallPatrolGroups) { + groups = this->aics[aicIndex].defWallPatrolGroups; + } + if (groups <= 0) { + continue; + } + + int stepSize = count / groups; + if (stepSize <= 0) { + stepSize = 1; + } + + for (int j = 0, patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]) { + continue; + } + + int slot + = (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + patrolGroupIndex) + % count; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType, + slot); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_01.cpp b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp new file mode 100644 index 000000000..dc4631bf4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp @@ -0,0 +1,150 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D60 + void AICState::setAICParameters_01(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].aiRequestDelay = 0x12; + this->aics[aicIndex].populationPerQuarry = 0x12; + this->aics[aicIndex].populationPerFarm = 6; + this->aics[aicIndex].maxWoodcutters = 6; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5500; + this->aics[aicIndex].highestPopularity = 7500; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 0x14; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 8; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 10; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 0x14; + this->aics[aicIndex].defWallPatrolGroups = 1; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x18; + this->aics[aicIndex].DefUnit2 = 0x18; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnit1 = 0x18; + this->aics[aicIndex].RaidUnit2 = 0x18; + this->aics[aicIndex].RaidUnit3 = 0x16; + this->aics[aicIndex].recruitProbDefDefault = 0; + this->aics[aicIndex].recruitProbDefStrong = 0; + this->aics[aicIndex].recruitProbRaidWeak = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x32; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 100; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].RaidUnitsRandom = 0xf; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0xf; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x16; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x10; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x18; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_PLAYER; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_02.cpp b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp new file mode 100644 index 000000000..281539c43 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7120 + void AICState::setAICParameters_02(int aicIndex) + { + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 0xffffffff; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xf; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0xc; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].populationPerWoodcutter = 5; + this->aics[aicIndex].populationPerQuarry = 0xf; + this->aics[aicIndex].resourceRebuildDelay = 0xc; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].maxFood = 0x96; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 6; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x30; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 500; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 400; + this->aics[aicIndex].unknown073 = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnitMax = 0x14; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 0; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitMeleeMin = 8; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 3; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].RaidUnit2 = 0x47; + this->aics[aicIndex].RaidUnit3 = 0x47; + this->aics[aicIndex].RaidUnit4 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x28; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 2; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 10; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x48; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x16; + this->aics[aicIndex].AttUnitSiegeDefMax = 0x1e; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_BALANCED; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_03.cpp b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp new file mode 100644 index 000000000..83053911a --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7520 + void AICState::setAICParameters_03(int aicIndex) + { + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 7000; + this->aics[aicIndex].highestPopularity = 0x2134; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 400; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 100; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x3c; + this->aics[aicIndex].maxWood = 0x46; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 0x19; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].OuterPatrolRallyDelay = 0x14; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x46; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x3c; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0xf; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 0x1e; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x1a; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 5; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_04.cpp b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp new file mode 100644 index 000000000..f2043fc77 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp @@ -0,0 +1,149 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7910 + void AICState::setAICParameters_04(int aicIndex) + { + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0x14; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 4; + this->aics[aicIndex].populationPerQuarry = 9; + this->aics[aicIndex].populationPerIronmine = 0xd; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 6; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 2; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 4; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0xfa; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidStrong = 10; + this->aics[aicIndex].recruitProbAttackDefault = 10; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].OuterPatrolRallyDelay = 3; + this->aics[aicIndex].RaidUnitsRandom = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].SortieUnitRangedMin = 8; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnitMax = 8; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 100; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].RaidUnit2 = 0x4a; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0xf; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0xc; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 8; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0xc; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 5; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_CLOSEST; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_05.cpp b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp new file mode 100644 index 000000000..52b7ad888 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7D20 + void AICState::setAICParameters_05(int aicIndex) + { + this->aics[aicIndex].maxFood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x14; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x14; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 2; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxWoodcutters = 0xe; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x14; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x22; + this->aics[aicIndex].recruitProbDefStrong = 0x19; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitMeleeMin = 7; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceSupportAllyThreshold = 8; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0x19; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].DefUnit1 = 0x4b; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].DefUnit5 = 0x4c; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].recruitProbRaidDefault = 0x21; + this->aics[aicIndex].recruitProbAttackDefault = 0x21; + this->aics[aicIndex].DefTotal = 0x46; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 1; + this->aics[aicIndex].AttCoordinationPreference = 0x3c; + this->aics[aicIndex].CowThrowInterval = 2000; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 5; + this->aics[aicIndex].AttUnit2 = 0x4a; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0x4c; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_06.cpp b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp new file mode 100644 index 000000000..cd71250b5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8110 + void AICState::setAICParameters_06(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x32; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerPitchrig = 7; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 10; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 5; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x78; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].SortieUnitRangedMin = 7; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x1e; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x3c; + this->aics[aicIndex].DefTotal = 0x3c; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x4c; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 0x1e; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0xf; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x46; + this->aics[aicIndex].AttForceRallyPercentage = 100; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 2; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 10; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 7; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x4b; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x46; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 5; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_07.cpp b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp new file mode 100644 index 000000000..eb1dbdaa2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8530 + void AICState::setAICParameters_07(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 1; + this->aics[aicIndex].taxesMax = 8; + this->aics[aicIndex].farm1 = 0; + this->aics[aicIndex].populationPerFarm = 100; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 500; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x1e; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x14; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 2; + this->aics[aicIndex].populationPerQuarry = 5; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 0xf; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].buildInterval = 5; + this->aics[aicIndex].resourceRebuildDelay = 0x50; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].DefDiggingUnitMax = 2; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].RecruitInterval = 8; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 0; + this->aics[aicIndex].SortieUnitRanged = 0x48; + this->aics[aicIndex].SortieUnitMeleeMin = 3; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].DefUnit1 = 0x48; + this->aics[aicIndex].DefUnit2 = 0x48; + this->aics[aicIndex].DefUnit3 = 0x4b; + this->aics[aicIndex].RaidUnitsBase = 0x14; + this->aics[aicIndex].RaidUnit1 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 5; + this->aics[aicIndex].AttForceRallyPercentage = 0x1e; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x28; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0; + this->aics[aicIndex].AttDiggingUnitMax = 0; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x78; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_08.cpp b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp new file mode 100644 index 000000000..020f996a2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp @@ -0,0 +1,149 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8920 + void AICState::setAICParameters_08(int aicIndex) + { + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 10; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x16; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x46; + this->aics[aicIndex].recruitProbAttackStrong = 0x5a; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefUnit3 = 0x19; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 10; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 2; + this->aics[aicIndex].AttCoordinationPreference = 10; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x10; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 6; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 3; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 4; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 2; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_09.cpp b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp new file mode 100644 index 000000000..3c462c06b --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8D20 + void AICState::setAICParameters_09(int aicIndex) + { + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 0x157c; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0xc; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 5; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x1e; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = -999; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 10; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].AttForceRandom = 4; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].SortieUnitRangedMin = 0xc; + this->aics[aicIndex].SortieUnitMelee = 0x1b; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnitsRandom = 0; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 6; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x28; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x12; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x17; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x17; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_10.cpp b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp new file mode 100644 index 000000000..58aebe7c6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9120 + void AICState::setAICParameters_10(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 6; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalWeak = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefTotal = 0x14; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefWalls = 4; + this->aics[aicIndex].DefUnit1 = 0x1c; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].DefUnit5 = 0x16; + this->aics[aicIndex].DefUnit6 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x28; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x3c; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0x18; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttUnitMain2 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_11.cpp b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp new file mode 100644 index 000000000..36c716dd8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp @@ -0,0 +1,145 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C94F0 + void AICState::setAICParameters_11(int aicIndex) + { + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].minimumApples = -1; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 6; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x1e; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].SortieUnitRanged = 0x4a; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].DefUnit2 = 0x4c; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x5a; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 9; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 10; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_12.cpp b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp new file mode 100644 index 000000000..c08fc909d --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9900 + void AICState::setAICParameters_12(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].maxFood = 0x50; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x4b; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine3 = 0x166; + this->aics[aicIndex].HarassingSiegeEnginesMax = 3; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x3d; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_13.cpp b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp new file mode 100644 index 000000000..c4318fa1f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C9CF0 + void AICState::setAICParameters_13(int aicIndex) + { + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0x14; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitMeleeMin = 0xf; + this->aics[aicIndex].SortieUnitMelee = 0x46; + this->aics[aicIndex].DefDiggingUnitMax = 0x3c; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x49; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0x28; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x49; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_14.cpp b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp new file mode 100644 index 000000000..bee77c542 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA0C0 + void AICState::setAICParameters_14(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 6; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 8; + this->aics[aicIndex].maxFood = 300; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x1e; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0x32; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackStrong = 0x14; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x1a; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].DefUnit4 = 0x4c; + this->aics[aicIndex].DefUnit5 = 0x49; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0x166; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x32; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 8; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].SiegeEngine5 = 0x4d; + this->aics[aicIndex].CowThrowInterval = 6; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x50; + this->aics[aicIndex].AttUnit2 = 0x1a; + this->aics[aicIndex].AttUnit2Max = 10; + this->aics[aicIndex].AttMaxAssassins = 10; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4c; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttUnitMain2 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_15.cpp b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp new file mode 100644 index 000000000..2d2c9deca --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004CA4D0 + void AICState::setAICParameters_15(int aicIndex) + { + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].farm4 = 0x21; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 7; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxResourceOther = 0xc; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 6; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 9; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].recruitProbRaidWeak = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].SortieUnitMelee = 0x1c; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].RaidUnitsBase = 0x10; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_16.cpp b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp new file mode 100644 index 000000000..1721cd187 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA8A0 + void AICState::setAICParameters_16(int aicIndex) + { + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 0x32; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 0; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x25; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x25; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 8; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x25; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp new file mode 100644 index 000000000..b6a98e489 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp @@ -0,0 +1,33 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0110 + void AICState::setAttackCoordinationLevel(int playerID) + { + int const _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == AITA_NULL) { + return; + } + int _willingnessPercentage = this->aics[_aiType - 1].AttCoordinationPreference + + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (90 < _willingnessPercentage) { + _willingnessPercentage = 90; + } + int _rngMod100 = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + if (_rngMod100 < _willingnessPercentage / 2) { + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel = 2; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel + = _rngMod100 < _willingnessPercentage; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp new file mode 100644 index 000000000..db0febac5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDFD0 + void AICState::setAttackPrimeInfo(int playerID, int attackedPlayerID) + { + int tile = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile; + DAT_TroopValueState::instance.attackInfo.startZone = (char)DAT_TileMapState::instance.AIZoneLayer[tile]; + DAT_TroopValueState::instance.attackInfo.startCon = (short)DAT_TileMapState::instance.PathConnectionLayer[tile]; + DAT_TroopValueState::instance.attackInfo.zoneSize + = DAT_PathFindingState::instance.zoneSizesArray[DAT_TroopValueState::instance.attackInfo.startCon]; + DAT_TroopValueState::instance.attackInfo.unknownOne_0x20f90 = 1; + DAT_TroopValueState::instance.attackInfo.keepCon + = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[attackedPlayerID].campground.tileEntry]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp new file mode 100644 index 000000000..ef07f7cf0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp @@ -0,0 +1,40 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDF10 + void AICState::setCurrentAttackRaidParameter(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + + int randomPercent = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk; + int raidUnits = this->aics[aicIndex].RaidUnitsRandom; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 1000) { + raidUnits = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + >= 5000) { + raidUnits *= 2; + } + + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].currentResources[Game::Resources::RT_GOLD] + < 500) { + raidUnits = -(this->aics[aicIndex].RaidUnitsBase / 2); + } + + DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + = (raidUnits * randomPercent) / 100; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp new file mode 100644 index 000000000..90217ac8e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDE60 + void AICState::setCurrentAttackStrength(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + int randomPercent = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + = (this->aics[aicIndex].AttForceRandom * randomPercent) / 100; + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave++; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] > 10000) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 7; + } else { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 5; + } + + if (DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength > 200) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 200; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp new file mode 100644 index 000000000..bf1b2a003 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF5E0 + void AICState::setEightTribe0xc0TribesToAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + for (int i = 0; i < 8; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[i + 0xc0]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 0xc0]) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + continue; + } + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp index d556d1026..780b9a3d4 100644 --- a/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp +++ b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp @@ -9,14 +9,16 @@ namespace OpenSHC { namespace AI { + using OpenSHC::AI::AITypeA; + using OpenSHC::Game::Resources::ResourceType; + // FUNCTION: STRONGHOLDCRUSADER 0x004CB060 void AICState::setFoodBuyPlan(int playerID) { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; // Early return if not an AI player (human player has aiType == 0) - if (aiType == AITA_NULL) { + if (aiType == AITypeA::AITA_NULL) { return; } @@ -28,36 +30,36 @@ namespace AI { // Apples: special handling with >= 0 check (allows -1 to disable) int const minimumApples = this->aics[aiConfigIndex].minimumApples; int* currentResources = DAT_GameState::ptr->playerDataArray[playerID].currentResources; - if (minimumApples >= 0 && currentResources[Game::Resources::RT_APPLE] < minimumApples) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_APPLE] + if (minimumApples >= 0 && currentResources[ResourceType::RT_APPLE] < minimumApples) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_APPLE] = this->aics[aiConfigIndex].tradeAmountFood; } // Cheese: standard check with > 0 int const minimumCheese = this->aics[aiConfigIndex].minimumCheese; - if (minimumCheese > 0 && currentResources[Game::Resources::RT_CHEESE] < minimumCheese) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_CHEESE] + if (minimumCheese > 0 && currentResources[ResourceType::RT_CHEESE] < minimumCheese) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_CHEESE] = this->aics[aiConfigIndex].tradeAmountFood; } // Bread: standard check with > 0 int const minimumBread = this->aics[aiConfigIndex].minimumBread; - if (minimumBread > 0 && currentResources[Game::Resources::RT_BREAD] < minimumBread) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_BREAD] + if (minimumBread > 0 && currentResources[ResourceType::RT_BREAD] < minimumBread) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_BREAD] = this->aics[aiConfigIndex].tradeAmountFood; } // Wheat: standard check with > 0 int const minimumWheat = this->aics[aiConfigIndex].minimumWheat; - if (minimumWheat > 0 && currentResources[Game::Resources::RT_WHEAT] < minimumWheat) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_WHEAT] + if (minimumWheat > 0 && currentResources[ResourceType::RT_WHEAT] < minimumWheat) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_WHEAT] = this->aics[aiConfigIndex].tradeAmountFood; } // Hops: standard check with > 0 int const minimumHop = this->aics[aiConfigIndex].minimumHop; - if (minimumHop > 0 && currentResources[Game::Resources::RT_HOPS] < minimumHop) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_HOPS] + if (minimumHop > 0 && currentResources[ResourceType::RT_HOPS] < minimumHop) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_HOPS] = this->aics[aiConfigIndex].tradeAmountFood; } } diff --git a/src/OpenSHC/AI/AICState/setInitialAIC.cpp b/src/OpenSHC/AI/AICState/setInitialAIC.cpp new file mode 100644 index 000000000..0f1970d6d --- /dev/null +++ b/src/OpenSHC/AI/AICState/setInitialAIC.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D18B0 + void AICState ::setInitialAIC() + { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::wipeAICMemory, this)(); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_01, this)(1); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_02, this)(2); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_03, this)(3); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_04, this)(4); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_05, this)(5); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_06, this)(6); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_07, this)(7); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_08, this)(8); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_09, this)(9); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_10, this)(10); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_11, this)(0xb); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_12, this)(0xc); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_13, this)(0xd); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_14, this)(0xe); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_15, this)(0xf); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_16, this)(0x10); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp new file mode 100644 index 000000000..01b79f53f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB1B0 + int AICState::setNextFarmTypeToBuild(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex++; + if (DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex >= 8) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + if ((&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex] == 0) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + return (&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp new file mode 100644 index 000000000..56eda5317 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE3B0 + void AICState::setNextMoveLocationForUnits(int playerID) + { + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 4000, FALSE); + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::setDestinationPairsBasedOnPreviousSearch, + DAT_PathFindingState::ptr)( + playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp new file mode 100644 index 000000000..f9d369e1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp @@ -0,0 +1,106 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CACB0 + void AICState::setTargetForTribeBasedOnTargetChoiceLogic(int owner, int tribe) + { + if (DAT_GameState::instance.playerDataArray[owner].aiType == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[owner].aiType - 1; + + int targetPlayer = 0; + int minDistance = 10000; + int maxGold = 0; + int minPoints = 1000000; + int ownerKeepX = DAT_GameState::instance.playerDataArray[owner].campground.xEntry; + int ownerKeepY = DAT_GameState::instance.playerDataArray[owner].campground.yEntry; + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + + if (this->aics[aicIndex].TargetChoice == AIC::AITC_PLAYER) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] != -1 + && DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] + >= maxGold) { + maxGold + = DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD]; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[player].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[player].totalTroopValue + + DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= minPoints) { + minPoints = points; + targetPlayer = player; + } + } + } + + if (targetPlayer == 0) { + // Fallback: pick the closest enemy with a living lord + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } + + if (targetPlayer == 0) { + return; + } + } + + DAT_TribesState::instance.tribes[tribe].targetPlayer_outpost = (short)targetPlayer; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp new file mode 100644 index 000000000..bedc117df --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF560 + void AICState::setTribe0x11ToAggressiveAndAttackIfConditionMet(int playerID) + { + if (DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].moat2 <= 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[17]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[17]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f7); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp new file mode 100644 index 000000000..c60bbedf3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp @@ -0,0 +1,37 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF400 + void AICState::setTribe0xCtoAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp new file mode 100644 index 000000000..1dc2cf667 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp @@ -0,0 +1,37 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF6E0 + void AICState::setTribe0xbToAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp new file mode 100644 index 000000000..45c33ff67 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp @@ -0,0 +1,17 @@ +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1090 + void AICState::setTriggerForAITauntResponse() + { + if (MACRO_CALL_MEMBER(Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) { + this->aiTauntResponseTimeWindow = timeGetTime(); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp new file mode 100644 index 000000000..94036fdec --- /dev/null +++ b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Game/Resources/ResourceTypeShort.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Game::Resources::ResourceTypeShort; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBE30 + void AICState::setupWorkshopProductionType(int playerID, int buildingID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AIT_NULL) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_BLACKSMITH) { + int setting = this->aics[aicIndex].blacksmithSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_BLACKSMITH); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SWORD) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SWORD; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SWORD; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_MACE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_MACE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_POLETURNER) { + int setting = this->aics[aicIndex].poleturnerSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_POLETURNER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SPEAR) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SPEAR; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SPEAR; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_PIKE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_PIKE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_FLETCHER) { + int setting = this->aics[aicIndex].fletcherSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_FLETCHER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_BOW) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_BOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_BOW; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_CROSSBOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_CROSSBOW; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp new file mode 100644 index 000000000..da204a269 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp @@ -0,0 +1,77 @@ +// disable deprecation warnings for strcpy +#pragma warning(disable : 4996) + +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/DE/SHCDE/eTextSections.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + // FIXME: The original function never sets a return value (eax is left over from the last computation), + // so it was most likely a void function. The header declares int, which forces the "return 0"s below + // and costs an extra "xor eax, eax" in the assembly. Change the return type to void in the header. + + // FUNCTION: STRONGHOLDCRUSADER 0x004D05D0 + void AICState::shareGoldAmongTeamMembers(int playerID, int gold) + { + int candidateCount = 0; + int candidates[9]; + for (int i = 1; i < 9; i++) { + if ((DAT_GameSynchronyState::instance.currentAIArray[i] != 0 + || DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) + && i != playerID + && DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID] + && DAT_GameState::instance.playerDataArray[i].lordKilledByPlayerID == 0) { + candidates[candidateCount] = i; + candidateCount++; + } + } + + if (candidateCount == 0) { + return; + } + + int goldPerPlayer = gold / candidateCount; + if (goldPerPlayer == 0) { + return; + } + + for (int j = 0; j < candidateCount; j++) { + int targetPlayer = candidates[j]; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[targetPlayer] != -1) { + // "Goods received from ally" + strcpy(DAT_GameSynchronyState::instance.receivedChatMessage, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_ALLIES2, 1)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, 0); + + // "Gold" + MACRO_CALL(OS_Func::_sprintf)(DAT_GameSynchronyState::instance.receivedChatMessage, "%d %s - ", + goldPerPlayer, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_GOODS, Game::Resources::RT_GOLD)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= goldPerPlayer; + DAT_GameState::instance.playerDataArray[targetPlayer].currentResources[Game::Resources::RT_GOLD] + += goldPerPlayer; + } + return; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp new file mode 100644 index 000000000..ea63343e2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB890 + BOOLEnum AICState::shouldDestroyWoodCutterUnk(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return FALSE; + } + + int buildingID; + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) { + buildingID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT); + } else { + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] <= 20) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 2) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].someCount10 < 25) { + return FALSE; + } + buildingID = DAT_GameState::instance.playerDataArray[playerID].someCount11; + } + + if (buildingID != 0) { + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + (short)DAT_BuildingsState::instance.buildings[buildingID].x, + (short)DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)( + buildingID); + return TRUE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp new file mode 100644 index 000000000..51ca93934 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA60 + BOOLEnum AICState::shouldNotBeBuildingArmoury(PlayerID playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_ARMOURY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].isArmouryAlmostFilledUp == 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp new file mode 100644 index 000000000..5ef58f276 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA30 + BOOLEnum AICState::shouldNotBeBuildingGranary(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_GRANARY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].granaryIsAlmostFilledUp == FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp new file mode 100644 index 000000000..d14e60a15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB5B0 + BOOLEnum AICState::shouldNotBeBuildingHovel(PlayerID playerID, Commands::MappersEnum param_2) + { + if (param_2 != Commands::M_MAPPER_HOVEL) { + return FALSE; + } + + int const populationCap = DAT_GameState::instance.playerDataArray[playerID].populationCap; + if (populationCap <= 12) { + return FALSE; + } + + if (populationCap <= DAT_GameState::instance.playerDataArray[playerID].currentPopulation) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 5) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk >= 5) { + return TRUE; + } + + return DAT_GameState::instance.playerDataArray[playerID].popularity < 5000; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp new file mode 100644 index 000000000..d44c311ef --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA90 + BOOLEnum AICState::shouldNotBuildKillingPits(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_KILLING_PIT) { + return DAT_AIVState::instance.mapExtraInfo.playerTotalKillingPits[playerID] >= 100; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp new file mode 100644 index 000000000..c1ac9a6b1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB9F0 + BOOLEnum AICState::shouldNotBuildMarketArmoryGranaryBarracksMercenarypost( + int playerID, Commands::MappersEnum param_2) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + // nervous: reject market/mercenarypost/barracks/armory types + if (param_2 != Commands::M_MAPPER_TRADEPOST && param_2 != Commands::M_MAPPER_BARRACKS_ARAB + && param_2 != Commands::M_MAPPER_BARRACKS_EURO && param_2 != Commands::M_MAPPER_ARMOURY) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } else { + // not nervous: only reject if destroy tracker > 0 and not marketplace + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker > 0 + && param_2 != Commands::M_MAPPER_TRADEPOST) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp new file mode 100644 index 000000000..83caadc1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp @@ -0,0 +1,50 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB700 + BOOLEnum AICState::shouldWaitWithBuildingAPositiveFearFactor(int playerID, Commands::MappersEnum param_2) + { + switch (param_2) { + case Commands::M_MAPPER_GARDEN1: + case Commands::M_MAPPER_GARDEN2: + case Commands::M_MAPPER_GARDEN3: + case Commands::M_MAPPER_GARDEN4: + case Commands::M_MAPPER_GARDEN5: + case Commands::M_MAPPER_GARDEN6: + case Commands::M_MAPPER_GARDEN7: + case Commands::M_MAPPER_GARDEN8: + case Commands::M_MAPPER_GARDEN9: + case Commands::M_MAPPER_GARDEN10: + case Commands::M_MAPPER_GARDEN11: + case Commands::M_MAPPER_GARDEN12: + case Commands::M_MAPPER_MAYPOLE: + case Commands::M_MAPPER_STATUE1: + case Commands::M_MAPPER_STATUE2: + case Commands::M_MAPPER_STATUE3: + case Commands::M_MAPPER_STATUE4: + case Commands::M_MAPPER_STATUE5: + case Commands::M_MAPPER_SHRINE1: + case Commands::M_MAPPER_SHRINE2: + case Commands::M_MAPPER_SHRINE3: + case Commands::M_MAPPER_SHRINE4: + case Commands::M_MAPPER_SHRINE5: + case Commands::M_MAPPER_DANCING_BEAR: + case Commands::M_MAPPER_POND1: + case Commands::M_MAPPER_POND2_SMALL: + case Commands::M_MAPPER_POND3_LARGE1: + case Commands::M_MAPPER_POND4_LARGE2: + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay += 1; + if (DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay < 200) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay = 0; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp new file mode 100644 index 000000000..768c6f8de --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB610 + BOOLEnum AICState::shouldWaitWithBuildingNegativeFearFactor(int playerID, Commands::MappersEnum mapper) + { + switch (mapper) { + case Commands::M_MAPPER_GALLOWS: + case Commands::M_MAPPER_STOCKS: + case Commands::M_MAPPER_CESS_PIT1: + case Commands::M_MAPPER_CESS_PIT2: + case Commands::M_MAPPER_CESS_PIT3: + case Commands::M_MAPPER_CESS_PIT4: + case Commands::M_MAPPER_BURNING_STAKE: + case Commands::M_MAPPER_GIBBET: + case Commands::M_MAPPER_DUNGEON: + case Commands::M_MAPPER_RACK_STRETCHING: + case Commands::M_MAPPER_RACK_FLOGGING: + case Commands::M_MAPPER_CHOPPING_BLOCK: + case Commands::M_MAPPER_DUNKING_STOOL: + if (DAT_GameState::instance.playerDataArray[playerID].popularity < 5000) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay++; + if (DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay < 100) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay = 0; + return FALSE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp new file mode 100644 index 000000000..f5c367225 --- /dev/null +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -0,0 +1,62 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC990 + int AICState::smallestTribeOfUnitType(int playerID, AIVUnitType unitType) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return 0; + } + + int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = maxGroups; + } + + int smallestTribeID = 0; + int smallestIndex = 0; + int smallestSize = 1000; + int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; + for (int i = 0; i < count; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i]) { + // FIXME: The original binary jumps from here directly into the shared assignment code at the + // end of the function, skipping the "smallestTribeID == 0" check. A goto reproduces this and + // matches the assembly much better (~55% vs ~41%), but we avoid goto, so the assignment is + // duplicated here. + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)( + playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + smallestTribeID = tribeID; + smallestIndex = i; + } + } + + if (smallestTribeID == 0) { + return 0; + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + smallestIndex] = (short)smallestTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + smallestIndex] + = DAT_TribesState::instance.tribes[smallestTribeID].uid; + return smallestTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp new file mode 100644 index 000000000..f6194c79a --- /dev/null +++ b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp @@ -0,0 +1,127 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Commands/GameCommandType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Commands::GameCommandType; + using OpenSHC::Game::GameMode; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D10B0 + void AICState::someChatMessageSelection() + { + int candidates[9]; + int candidateCount; + int total; + int i; + int playerID; + + if ((MACRO_CALL_MEMBER(OpenSHC::Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) + && (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SKIRMISH_SINGLE_PLAYER)) { + + if (this->aiTauntResponseTimeWindow == 0) { + return; + } + + if (timeGetTime() - this->aiTauntResponseTimeWindow <= 2000) { + return; + } + + candidateCount = 0; + // Players 1..8, manually unrolled by 4 in the original. + for (playerID = 1; playerID < 9; playerID += 4) { + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 1] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 1] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 1; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 2] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 2] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 2; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 3] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 3] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 3; + candidateCount++; + } + } + + if (candidateCount > 0) { + total = 0; + // NOTE: 'i = total' keeps the (redundant) loop entry check of the original. + for (i = total; i < candidateCount; i++) { + total += DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[candidates[i]].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + } + + if (total != 0) { + int roll = (int)SEC_RNG::instance.currentNumber1; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber1, SEC_RNG::ptr)(); + roll = roll % total; + + for (i = 0; i < candidateCount; i++) { + playerID = candidates[i]; + roll -= DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + if (roll < 0) { + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[1] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[2] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[3] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[4] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[5] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[6] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[7] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[8] = 1; + DAT_GameSynchronyState::instance.DAT_ChatTauntOrMessage = -2 + - DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeCount; + DAT_GameSynchronyState::instance.DAT_GameCommandParam0 = playerID; + MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::queueCommand, + DAT_GameSynchronyState::ptr)(OpenSHC::Commands::GCT_TAUNT_OR_CHAT); + break; + } + } + } + } + } + + this->aiTauntResponseTimeWindow = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp new file mode 100644 index 000000000..0a37073ba --- /dev/null +++ b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC7B0 + void AICState::syncBehaviourType7UnitTribes(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 7) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp new file mode 100644 index 000000000..480816f06 --- /dev/null +++ b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0190 + BOOLEnum AICState::teamIsWeakRelativeToEnemy(int playerID, BOOLEnum param_2) + { + // strength of the own team in percent of the enemy strength + int _relativeStrength; + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue == 0) { + _relativeStrength = 100; + } else { + _relativeStrength = DAT_GameState::instance.playerDataArray[playerID].totalTeamTroopValue * 100 + / DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + } + DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy = _relativeStrength; + + // the team counts as weak below a threshold that is lower with an enclosed keep + // and much higher during the first 12000 ticks + if (param_2) { + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 70) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 300) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 100) { + return FALSE; + } + } else { + // without enemy ranged troops there is no reason to become nervous + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyRangedTroopValue < 100) + return FALSE; + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 50) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 250) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 75) { + return FALSE; + } + } + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp new file mode 100644 index 000000000..e71dc1e49 --- /dev/null +++ b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/Skirmish/SkirmishDefinedData.hpp" + +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Game::Skirmish::SkirmishDefinedData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC8D0 + undefined4 AICState::unitIDIsRangedOrArmored(int unitID) + { + int _unitType = DAT_UnitsState::instance.units[unitID].unitType; + + for (int i = 0; i < 7; i++) { + if (DAT_SkirmishDefinedData::instance.RangedAndArmoredUnits[i] == _unitType) { + return TRUE; + } + } + + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp new file mode 100644 index 000000000..eef4ddd57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D53E0 + void AICState::updateAIBehaviour() + { + if (DAT_GameState::instance.mapAndTime.dayTicks == 10) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeOutpostTribesAttack, this)(); + + // every AI gets its own tick of the day, week and month, so the work is spread out + for (int _playerID = 1; _playerID < 9; _playerID++) { + if (DAT_GameState::instance.playerDataArray[_playerID].aiType == OpenSHC::AI::AIT_NULL) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].playerDeathRelated != 0) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].lordKilledByPlayerID != 0) + continue; + int _aicIndex = DAT_GameState::instance.playerDataArray[_playerID].aiType - 1; + + if (DAT_GameState::instance.mapAndTime.dayTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieRangedUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieMeleeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recruitHarrassingSiegeEngines, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignMoatDiggers, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCreateSiegeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineers, this)(_playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiBuyAndSellGoods, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveRaidInstructions, this)(_playerID); + } + + if (DAT_GameState::instance.mapAndTime.weekTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineerForOilDuty, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateTribeLocations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIPlayerState, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiUpdateTaxesAndRations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setFoodBuyPlan, this)(_playerID); + + // on the month tick the strength update happens below, with the nervousness handling enabled + if (DAT_GameState::instance.mapAndTime.monthTicks != _playerID) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 0); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay != 0) + DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay--; + if (DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown != 0) + DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6--; + } + + if (DAT_GameState::instance.mapAndTime.monthTicks == _playerID) { + MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countMoatsOwnedByEachPlayer, DAT_TileMapState::ptr)(); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::instructTribe166ToMove, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToSortieUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectAttackTarget, this)(_playerID); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending = 1; + } else { + // only spend on recruiting while gold is above the AIC's threshold + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending + = DAT_GameState::instance.playerDataArray[_playerID].currentResources[0xf] + > this->aics[_aicIndex].recruitGoldThreshold; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp new file mode 100644 index 000000000..5050ed89e --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp @@ -0,0 +1,283 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Audio/SFX/SFXState.func.hpp" +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SFXState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D49E0 + void AICState::updateAIPlayerState(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + // an ally asked for help and is still alive: drop everything and go to their keep + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk != 1 + && DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToPlayerIDsKeepIfPossible, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk); + return; + } + + int _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + if (_aiPlayerState == 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( + playerID)) { + // send own units back to keep or campfire + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRetreatUnits, this)(playerID); + return; + } + + // proceed with attack + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); + // fixme: struct layout. This looks like an int[10][8] attack tracker starting at + // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). + (&DAT_GameState::instance + .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .attackedByPlayerArrayUnk)[playerID * 8]++; + } else if ((MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID) + && _aiPlayerState != 9) + || (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && _aiPlayerState <= 5) + || DAT_GameState::instance.playerDataArray[playerID].attackTicker >= 200) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 8; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + + if (!MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // attacked player is dead + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState <= 3) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)( + playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState < 7) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 7; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearOutdatedAITribes, this)(playerID); + + _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + + if (_aiPlayerState == 1) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recreateEngineersTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::initializeGatePathfindingForOwner, + DAT_BuildingsState::ptr)(playerID); + // computes the first siege location for units + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeEnemyKeepApproachTile, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile > 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackTribesToLocations, this)(playerID); + return; + } + // no approach tile found: back to idle + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + return; + } + + if (_aiPlayerState == 2) { + // gathering: wait until enough of the attack force has rallied + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiChoiceAttForceRallyPercentage, this)(playerID)) + return; + // send the attacking units to move closer + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 3; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setNextMoveLocationForUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0xe_toPlaceTunnels, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playTauntToAttackedPlayerBikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 3) { + // send the attacking units closer to the castle + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::processAttForceRallyPercentage, this)(playerID)) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 4; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + return; + } + + if (_aiPlayerState == 4) { + // siege: wait AttAssaultDelay (skipped without siege engines) before the assault + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::countSiegeEngineUnits, this)(playerID) <= 0) + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 100; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue++; + if (DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue + <= this->aics[_aicIndex].AttAssaultDelay) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Audio::SFX::SFXState_Func::playSFXAtLocationFullVolume, DAT_SFXState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, 0x14); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // castle is closed: attack the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe11StandGroundToGate, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe13DefensiveWave, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 5) { + // attack walls + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01++; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 >= 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandWallAssaultTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0x11ToAggressiveAndAttackIfConditionMet, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setEightTribe0xc0TribesToAggressiveAndAttack, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xCtoAggressiveAndAttack, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xbToAggressiveAndAttack, this)(playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + && !MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 6) { + // enemy castle breached + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + int _attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(_attackedPlayerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToAttackBreachedCastle, this)(playerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + // the breach got closed again: back to attacking the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + + if (_aiPlayerState == 7) { + // target died mid-attack: linger for AttUnitPatrolRecommandDelay, then retreat + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue++; + if (DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue + <= this->aics[_aicIndex].AttUnitPatrolRecommandDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + return; + } + + if (_aiPlayerState == 8) { + // retreat from attack + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playAnger1BikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 9) { + // retreating: back to idle once the units are home (or after 20 ticks) + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker < 20 + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::percentageNonMovingTribesGTEAICSpecified, this)( + playerID) + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp new file mode 100644 index 000000000..249bea57d --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp @@ -0,0 +1,212 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategory.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategoryInt.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AINervousDestroyBuildingCategory; + using OpenSHC::AI::AINervousDestroyBuildingCategoryInt; + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::Game::GameMode2; + using OpenSHC::Game::TrailType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3320 + void AICState::updateAIStrengthState(int playerID, int aiRecruitUnitChoicePreference) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == 0) + return; + + if ((char)aiRecruitUnitChoicePreference == 0) { + if (DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER) + return; + if (DAT_GameCore::instance.isSkirmishTrail != TRUE) + return; + if (DAT_GameCore::instance.currentTrailType != OpenSHC::Game::TT_EXTREME) + return; + } + + int _aicIndex = _aiType - 1; + + int _currentGold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + if (_currentGold < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + else if (_currentGold < 2000) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 2; + + int _currentPopulation = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (_currentPopulation >= 15) { + if (_currentPopulation < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalFood = DAT_GameState::instance.playerDataArray[playerID].totalFood; + if (_totalFood >= 20) { + if (_totalFood < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalTroopCount = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk + + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk; + if (_totalTroopCount >= 8) { + if (_totalTroopCount < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 4; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 2) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 7) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_DEFAULT; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_STRONG; + + // while the first AIV pause is still running, the AI always feels weak + if (DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[playerID] + .aivPauses[DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex] + > 0) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + + // the three recruitProb* triples are laid out as Default, Weak, Strong, matching AIStrengthType + int _aiStrengthFeeling = DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling; + int _recruitProbDefensive = (&this->aics[_aicIndex].recruitProbDefDefault)[_aiStrengthFeeling]; + int _recruitProbAttack = (&this->aics[_aicIndex].recruitProbAttackDefault)[_aiStrengthFeeling]; + int _recruitProbRaid = (&this->aics[_aicIndex].recruitProbRaidDefault)[_aiStrengthFeeling]; + int _totalRecruitProb = _recruitProbAttack + _recruitProbRaid + _recruitProbDefensive; + + int _aiRecruitUnitChoice; + if (_totalRecruitProb != 0) { + int _random = SEC_RNG::instance.currentNumber2 % _totalRecruitProb; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + if ((int)DAT_GameCore::instance.mapTimeInTicks < 4800) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbRaid + _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_RAIDING; + else + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else { + _aiRecruitUnitChoice = aiRecruitUnitChoicePreference; + } + + if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(_aicIndex, playerID)) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + >= this->aics[_aicIndex].DefTotal) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState != _aiRecruitUnitChoice) + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = _aiRecruitUnitChoice; + + if ((char)aiRecruitUnitChoicePreference == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, TRUE)) { + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 0; + return; + } + + int _gold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_gold > 500) { + if (_gold > 2500) { + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter++; + if ((DAT_GameState::instance.playerDataArray[playerID].aiStateCounter & 1) + && DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy + <= 20) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::shareGoldAmongTeamMembers, this)( + playerID, _gold / 10); + } + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 5) { + // unreachable in the original as well: anything <= 5 is already handled by the <= 7 branch + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 1) + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker--; + return; + } + + int _destroyChoice = DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker; + if (_destroyChoice > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 0; + return; + } + + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[2] = 20; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_destroyChoice == 8) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + } else if (_destroyChoice == 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (_destroyChoice == 6) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_GATEHOUSES_TOWERS_GUILDS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_BARRACKS_MERCPOST_STABLES); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WATER_BUILDINGS); + } else if (_destroyChoice == 5) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } else if (_destroyChoice == 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FARMS); + } + + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker--; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, FALSE)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous2BikFromPlayer, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 8; + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter = 0; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 8; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateTribeLocations.cpp b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp new file mode 100644 index 000000000..83f75e037 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" + +#include "OpenSHC/Globals/DAT_EntityState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4940 + void AICState::updateTribeLocations(int playerID) + { + AITypeInt _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + + if (_aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + 1; + + if (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + >= this->aics[_aiType - 1].defWallPatrolRallyTime) { + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + 1; + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter = 0; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::ifHasTribe0SendUnitsToKeep, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeAtOffset1ToCampfire, this)(playerID); + + if (DAT_GameState::instance.mapAndTime.monthTicks == playerID) { + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::digNearestMoat, this)(playerID); + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous, this)(playerID); + + MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendWallPatrolUnitTribesToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendOilMenForOilPickupOrToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveOuterPatrolCommand, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp new file mode 100644 index 000000000..01599292d --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp @@ -0,0 +1,105 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEFD0 + void AICState::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers(int playerID) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = _aiType - 1; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) + return; + + int _availableEngineers = DAT_TribesState::instance.tribes[_aiTribeID].size; + int _requiredEngineers; + int _totalRequiredEngineers = 0; + + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + break; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + break; + } + } + if (_totalRequiredEngineers + _requiredEngineers > _availableEngineers) + break; + _totalRequiredEngineers += _requiredEngineers; + } + + if (_totalRequiredEngineers <= 0) + return; + + for (; DAT_TribesState::instance.tribes[_aiTribeID].size != 0; _totalRequiredEngineers--) { + if (_totalRequiredEngineers <= 0) + break; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)( + _aiTribeID); + } + + MappersEnum _siegeEngineType; + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + return; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + _siegeEngineType + = (MappersEnum)DAT_SkirmishDefinedData::instance.SiegeEngineCommandBuildingTypes[j]; + break; + } + } + if (_requiredEngineers > _availableEngineers) + return; + _availableEngineers -= _requiredEngineers; + + int _tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToSelection, this)( + playerID, _requiredEngineers); + if (_tribeID == 0) + return; + + DAT_TribesState::instance.tribes[_tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[_tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)(_tribeID, _siegeEngineType, 60, (UnitInstructionType)15); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp new file mode 100644 index 000000000..f9a3597ee --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp @@ -0,0 +1,67 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF180 + void AICState::useAITribe_0xe_toPlaceTunnels(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xe]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xe]) + return; + int _countdown = DAT_TribesState::instance.tribes[_aiTribeID].size; + + while (DAT_TribesState::instance.tribes[_aiTribeID].size != 0) { + if (_countdown <= 0) + break; + + int _unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)(_aiTribeID); + // Yes this line has to be right here for the code to match. + _countdown--; + + int _tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + DAT_TribesState::instance.tribes[_tribeID].tribeType = OpenSHC::AI::Tribes::AITT_TUNNELERS; + + DAT_TribesState::instance.tribes[_tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x415; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + _unitID, _tribeID); + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)( + _tribeID, OpenSHC::Commands::M_MAPPER_TUNNEL_CONSTRUCTION, 50, (UnitInstructionType)21); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/wipeAICMemory.cpp b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp new file mode 100644 index 000000000..ed4a02035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D30 + void AICState::wipeAICMemory() + { + for (int i = 0; i < 20; i++) { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 676, '\0', &this->aics[i]); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/yieldCowThrow.cpp b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp new file mode 100644 index 000000000..c381e7b3e --- /dev/null +++ b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFF60 + BOOLEnum AICState::yieldCowThrow(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType != AITA_NULL) { + int aicIndex = aiType - 1; + if (DAT_GameState::instance.mapAndTime.skirmishNoCowThrowing == 0 + && this->aics[aicIndex].CowThrowInterval != 0 + && DAT_GameState::instance.playerDataArray[playerID].counter > 0) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter + > this->aics[aicIndex].CowThrowInterval) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter = 0; + return TRUE; + } + } + } + return FALSE; + } +} +} From 1ec63198f6b3f828e118a6ef627d63fc8f1040e8 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 27 Sep 2026 00:23:25 +0200 Subject: [PATCH 002/104] add status --- status/addresses-SHC-3BB0A8C1.txt | 336 +++++++++++++++--------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f6e47278b..c4c9b442a 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34157,73 +34157,73 @@ SHC_3BB0A8C1_0x004C6CD0 | 0.0% | Pending SHC_3BB0A8C1_0x004C6D30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C6D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004C6D60 | 92.2% | Constant store order differs -SHC_3BB0A8C1_0x004C7120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7120 | 83.7% | Constant store order differs -SHC_3BB0A8C1_0x004C7520 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7520 | 94.1% | Constant store order differs -SHC_3BB0A8C1_0x004C7910 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7910 | 81.9% | Constant store order differs -SHC_3BB0A8C1_0x004C7D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7D20 | 81.5% | Constant store order differs -SHC_3BB0A8C1_0x004C8110 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8110 | 84.3% | Constant store order differs -SHC_3BB0A8C1_0x004C8530 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8530 | 94.7% | Constant store order differs -SHC_3BB0A8C1_0x004C8920 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8920 | 87.5% | Constant store order differs -SHC_3BB0A8C1_0x004C8D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8D20 | 90.4% | Constant store order differs -SHC_3BB0A8C1_0x004C9120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9120 | 80.0% | Constant store order differs -SHC_3BB0A8C1_0x004C94F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C94F0 | 89.5% | Constant store order differs -SHC_3BB0A8C1_0x004C9900 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9900 | 84.2% | Constant store order differs -SHC_3BB0A8C1_0x004C9CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9CF0 | 83.3% | Constant store order differs -SHC_3BB0A8C1_0x004CA0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA0C0 | 82.3% | Constant store order differs -SHC_3BB0A8C1_0x004CA4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA4D0 | 74.9% | Constant store order differs -SHC_3BB0A8C1_0x004CA8A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA8A0 | 84.8% | Constant store order differs -SHC_3BB0A8C1_0x004CAC70 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAC70 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CACB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CACB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CAD10 | 0.0% | Pending SHC_3BB0A8C1_0x004CAE20 | 0.0% | Pending -SHC_3BB0A8C1_0x004CAEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAEA0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB060 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB150 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB150 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB1B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB1B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB220 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB220 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CB290 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB290 | 98.1% | Reimplemented -SHC_3BB0A8C1_0x004CB330 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB330 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB3A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB3A0 | 85.3% | Register allocation differs -SHC_3BB0A8C1_0x004CB4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB4D0 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB540 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB540 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB5B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB5B0 | 91.3% | Reimplemented -SHC_3BB0A8C1_0x004CB610 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB610 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB66C | 0.0% | Pending @@ -34501,7 +34501,7 @@ SHC_3BB0A8C1_0x004CB6FA | 0.0% | Pending SHC_3BB0A8C1_0x004CB6FB | 0.0% | Pending -SHC_3BB0A8C1_0x004CB700 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB700 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB754 | 0.0% | Pending @@ -34845,55 +34845,55 @@ SHC_3BB0A8C1_0x004CB803 | 0.0% | Pending SHC_3BB0A8C1_0x004CB804 | 0.0% | Pending -SHC_3BB0A8C1_0x004CB810 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB810 | 92.3% | Reimplemented -SHC_3BB0A8C1_0x004CB890 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB890 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB940 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB940 | 88.9% | Register allocation differs -SHC_3BB0A8C1_0x004CB9F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB9F0 | 93.3% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBA50 | 85.1% | Reimplemented functionally, stack and cached load differencesd -SHC_3BB0A8C1_0x004CBE30 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBE30 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CBFA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC000 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004CC000 | 92.9% | Reimplemented -SHC_3BB0A8C1_0x004CC070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC250 | 94.2% | Register scheduling differs SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC420 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC420 | 38.0% | Loop strength reduction diff -SHC_3BB0A8C1_0x004CC520 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC520 | 93.8% | Reimplemented -SHC_3BB0A8C1_0x004CC5A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC5A0 | 85.7% | Register allocation differs -SHC_3BB0A8C1_0x004CC680 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC680 | 83.3% | Register push placement diff SHC_3BB0A8C1_0x004CC6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC720 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC720 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC7B0 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC840 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC840 | 94.3% | Reimplemented -SHC_3BB0A8C1_0x004CC8D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC8D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC910 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented -SHC_3BB0A8C1_0x004CC990 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC990 | 40.90% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CCAF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCAF0 | 68.1% | Register allocation differs -SHC_3BB0A8C1_0x004CCBE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCD20 | 63.9% | Loop register allocation diff SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending @@ -35265,175 +35265,175 @@ SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending SHC_3BB0A8C1_0x004CD070 | 0.0% | Pending -SHC_3BB0A8C1_0x004CD0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD160 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD160 | 91.7% | Register allocation differs -SHC_3BB0A8C1_0x004CD250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD250 | 94.7% | Reimplemented -SHC_3BB0A8C1_0x004CD2A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD2A0 | 59.60% | Reimplemented functionally, code block ordering problem -SHC_3BB0A8C1_0x004CD560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CD690 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented -SHC_3BB0A8C1_0x004CD7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD7B0 | 76.1% | Register allocation differs -SHC_3BB0A8C1_0x004CD8F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD8F0 | 74.2% | Register allocation differs -SHC_3BB0A8C1_0x004CDA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDAB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDB20 | 23.3% | Loop/register structure differs -SHC_3BB0A8C1_0x004CDBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDBC0 | 65.0% | Loop counter spilled in orig -SHC_3BB0A8C1_0x004CDCA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCF0 | 41.8% | Register allocation differs -SHC_3BB0A8C1_0x004CDE60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDE60 | 76.1% | AIC index folding differs -SHC_3BB0A8C1_0x004CDF10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDF10 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004CDFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE040 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE040 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE1A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE1A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE200 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE200 | 55.3% | Register/store scheduling diff SHC_3BB0A8C1_0x004CE270 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE3B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE3B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE430 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE430 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE4D0 | 93.8% | Reimplemented SHC_3BB0A8C1_0x004CE530 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE5F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE5F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE660 | 0.0% | Pending SHC_3BB0A8C1_0x004CE6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE790 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE790 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004CE830 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE860 | 0.0% | Pending SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE930 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE930 | 87.0% | Register allocation differs -SHC_3BB0A8C1_0x004CEA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEA50 | 92.7% | Reimplemented functionally, stack ordering problem -SHC_3BB0A8C1_0x004CEB50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEB50 | 98.8% | Reimplemented SHC_3BB0A8C1_0x004CEB80 | 0.0% | Pending -SHC_3BB0A8C1_0x004CEC80 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEC80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CED90 | 0.0% | Pending +SHC_3BB0A8C1_0x004CED90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEEA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEFD0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF060 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF270 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF270 | 62.2% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF2E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF2E0 | 46.8% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF400 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF470 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF470 | 56.0% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF4F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF4F0 | 57.10% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF560 | 37.74% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF660 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF660 | 97.2% | Reimplemented -SHC_3BB0A8C1_0x004CF6E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF6E0 | 62.22% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CF750 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF750 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF7C0 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF7C0 | 98.2% | Reimplemented SHC_3BB0A8C1_0x004CF7F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF920 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF920 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFA10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFA10 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CFBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFCD0 | 56.0% | Unresolved attackInfo offsets -SHC_3BB0A8C1_0x004CFF60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D00A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D00A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0190 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0190 | 100.0% | Reimplemented, compiler didn't optimize one tail -SHC_3BB0A8C1_0x004D0280 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0280 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004D0520 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0580 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0580 | 95.7% | Reimplemented -SHC_3BB0A8C1_0x004D05D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D05D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0720 | 0.0% | Pending -SHC_3BB0A8C1_0x004D07C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D07C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0830 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D08B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D08B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0910 | 0.0% | Pending -SHC_3BB0A8C1_0x004D0A20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0A80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0B10 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B10 | 68.0% | Load scheduling differs -SHC_3BB0A8C1_0x004D0B70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0BB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0BB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0D40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0D40 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0DB0 | 0.0% | Pending @@ -35447,103 +35447,103 @@ SHC_3BB0A8C1_0x004D0EF0 | 0.0% | Pending SHC_3BB0A8C1_0x004D0F40 | 0.0% | Pending -SHC_3BB0A8C1_0x004D0F90 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0FC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0FC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1000 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1050 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1050 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1090 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1090 | 90.0% | Reimplemented -SHC_3BB0A8C1_0x004D10B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D10B0 | 100.0% | Reimplemented, one reordered push edi SHC_3BB0A8C1_0x004D11C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D12A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1320 | 69.88% | Partially reimplemented, stuck SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending SHC_3BB0A8C1_0x004D1710 | 0.0% | Pending -SHC_3BB0A8C1_0x004D18B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D18B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1950 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004D1A00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2000 | 29.4% | Register allocation differs SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending SHC_3BB0A8C1_0x004D20C0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D2500 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented -SHC_3BB0A8C1_0x004D2660 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2660 | 59.6% | Branch layout differs -SHC_3BB0A8C1_0x004D2730 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2730 | 79.2% | AIC index folding differs -SHC_3BB0A8C1_0x004D2790 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D2790 | 91.3% | Reimplemented -SHC_3BB0A8C1_0x004D27E0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D27E0 | 92.0% | Reimplemented SHC_3BB0A8C1_0x004D2830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2870 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D28B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D28B0 | 73.0% | Register allocation differs -SHC_3BB0A8C1_0x004D2A70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2A70 | 30.0% | Register allocation differs -SHC_3BB0A8C1_0x004D2E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs -SHC_3BB0A8C1_0x004D2F80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D30E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented, barring one register usage difference -SHC_3BB0A8C1_0x004D3320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effectively 100% -SHC_3BB0A8C1_0x004D3780 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3780 | 74.0% | Constant 1 kept in register -SHC_3BB0A8C1_0x004D3850 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3850 | 82.3% | AIC index folding differs -SHC_3BB0A8C1_0x004D38C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D38C0 | 83.8% | AIC index folding differs SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3960 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D39B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3AE0 | 35.8% | Block layout differs -SHC_3BB0A8C1_0x004D3F20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented -SHC_3BB0A8C1_0x004D4040 | 100.0% | Reimplemented, for loop didn't work +SHC_3BB0A8C1_0x004D4040 | 97.0% | Reimplemented -SHC_3BB0A8C1_0x004D40B0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D40B0 | 94.9% | Reimplemented SHC_3BB0A8C1_0x004D4130 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4220 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4220 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4340 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4340 | 54.63% | Reimplemented, different register usage SHC_3BB0A8C1_0x004D4400 | 0.0% | Pending -SHC_3BB0A8C1_0x004D44E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D44E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4530 | 0.0% | Pending @@ -35551,15 +35551,15 @@ SHC_3BB0A8C1_0x004D45E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4630 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4680 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4680 | 64.80% | Stuck, register usage SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending -SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs -SHC_3BB0A8C1_0x004D4940 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D49E0 | 52.84% | Register allocation differences, logic checks out SHC_3BB0A8C1_0x004D5010 | 100.0% | Reimplemented @@ -35571,7 +35571,7 @@ SHC_3BB0A8C1_0x004D5370 | 0.0% | Pending SHC_3BB0A8C1_0x004D53A0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D53E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D53E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D55D0 | 0.0% | Pending From 5dd535fd7ae99b5ee912e8d0bb75d61d4e9152bd Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:20 +0200 Subject: [PATCH 003/104] reimplement: SHC_3BB0A8C1_0x004D0580 100.00% Reimplemented --- src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp index b8de9aefa..835f96773 100644 --- a/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp +++ b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp @@ -14,7 +14,7 @@ namespace AI { continue; if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) continue; - if (DAT_GameState::instance.playerDataArray[playerID].aiType - 1 != aicIndex) + if (aicIndex != DAT_GameState::instance.playerDataArray[playerID].aiType - 1) continue; return TRUE; } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c4c9b442a..3c0676ce3 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35403,7 +35403,7 @@ SHC_3BB0A8C1_0x004D0280 | 98.2% | Reimplemented SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0580 | 95.7% | Reimplemented +SHC_3BB0A8C1_0x004D0580 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D05D0 | 100.0% | Reimplemented From 7dcb33d99a89fbbba3c47c5cdaf41084cd28a143 Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:20 +0200 Subject: [PATCH 004/104] reimplement: SHC_3BB0A8C1_0x004CC520 100.00% Reimplemented --- src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp index 23c4bf441..ba7ee99f2 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp @@ -21,7 +21,7 @@ namespace AI { return; if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) return; - if (this->aics[aiType - 1].recruitGoldThreshold + if (this->aics[aiType - 1].defSiegeEngineGoldThreshold + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 30) return; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 3c0676ce3..c1ff0ca15 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34869,7 +34869,7 @@ SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending SHC_3BB0A8C1_0x004CC420 | 38.0% | Loop strength reduction diff -SHC_3BB0A8C1_0x004CC520 | 93.8% | Reimplemented +SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC5A0 | 85.7% | Register allocation differs From e0c73b95662b2e62b81e1792e4e828096149f01e Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:20 +0200 Subject: [PATCH 005/104] reimplement: SHC_3BB0A8C1_0x004CDB20 34.34% regalloc/spill differs --- ...mBuildingIDFromPlayersArrayOfBuildings.cpp | 23 ++++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp index ffc6b981f..a54e7c265 100644 --- a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -22,18 +22,19 @@ namespace AI { int buildingType = DAT_BuildingsState::instance.buildings[buildingID].buildingType; for (int j = 0; j < chosenPriority; j++) { - bool isMatch; - if (rng <= 1) - isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]; - else if (rng <= 3) - isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]; - else - isMatch = buildingType == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]; - - if (isMatch) { - chosenBuildingID = buildingID; - chosenPriority = j; + if (rng <= 1) { + if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]) + continue; + } else if (rng <= 3) { + if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]) + continue; + } else { + if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + continue; } + + chosenBuildingID = buildingID; + chosenPriority = j; } } return chosenBuildingID; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c1ff0ca15..46faf41c9 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35287,7 +35287,7 @@ SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 23.3% | Loop/register structure differs +SHC_3BB0A8C1_0x004CDB20 | 34.3% | regalloc/spill differs SHC_3BB0A8C1_0x004CDBC0 | 65.0% | Loop counter spilled in orig From 899e3c8795b582134481223c66cee207353ca442 Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:21 +0200 Subject: [PATCH 006/104] reimplement: SHC_3BB0A8C1_0x004CC250 100.00% Reimplemented --- .../getUnitTypeForUnitBehaviourType.cpp | 29 ++++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp index 3a4accdb0..fd1ea735f 100644 --- a/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp +++ b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp @@ -37,20 +37,21 @@ namespace AI { return (UnitType)this->aics[aicIndex].AttUnitEngage; if (unitBehaviourType == OpenSHC::AI::AIUBT_SIEGEDEFENSE) return (UnitType)this->aics[aicIndex].AttUnitSiegeDef; - if (unitBehaviourType != OpenSHC::AI::AIUBT_ATTUNITMAIN) - return (UnitType)this->aics[aicIndex].AttUnitMain1; - - if ((&this->aics[aicIndex] - .AttUnitMain1)[DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex] - == 0) - DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; - if (DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex >= 4) - DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; - - int choice = DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex; - UnitType unitType = (UnitType)(&this->aics[aicIndex].AttUnitMain1)[choice]; - DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = choice + 1; - return unitType; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ATTUNITMAIN) { + if ((&this->aics[aicIndex] + .AttUnitMain1)[DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex >= 4) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + + int choice = DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex; + UnitType unitType = (UnitType)(&this->aics[aicIndex].AttUnitMain1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = choice + 1; + return unitType; + } + + return (UnitType)this->aics[aicIndex].AttUnitMain1; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 46faf41c9..4fd3b7e49 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34863,7 +34863,7 @@ SHC_3BB0A8C1_0x004CC000 | 92.9% | Reimplemented SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC250 | 94.2% | Register scheduling differs +SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending From bb39059be190c7134ac0106babfea01ea971764a Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:21 +0200 Subject: [PATCH 007/104] reimplement: SHC_3BB0A8C1_0x004CB5B0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp index d14e60a15..b674070c3 100644 --- a/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp @@ -18,7 +18,7 @@ namespace AI { return FALSE; } - if (populationCap <= DAT_GameState::instance.playerDataArray[playerID].currentPopulation) { + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation < populationCap) { return TRUE; } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 4fd3b7e49..a9a7c19ff 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34221,7 +34221,7 @@ SHC_3BB0A8C1_0x004CB4D0 | 97.5% | Reimplemented SHC_3BB0A8C1_0x004CB540 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB5B0 | 91.3% | Reimplemented +SHC_3BB0A8C1_0x004CB5B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB610 | 100.0% | Reimplemented From 06716bc662fd0278b003e12aa41b5808261efa0d Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:22 +0200 Subject: [PATCH 008/104] reimplement: SHC_3BB0A8C1_0x004CB9F0 100.00% Reimplemented --- ...shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp | 4 ++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp index c1ac9a6b1..8d1ab8554 100644 --- a/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp +++ b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp @@ -12,8 +12,8 @@ namespace AI { { if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { // nervous: reject market/mercenarypost/barracks/armory types - if (param_2 != Commands::M_MAPPER_TRADEPOST && param_2 != Commands::M_MAPPER_BARRACKS_ARAB - && param_2 != Commands::M_MAPPER_BARRACKS_EURO && param_2 != Commands::M_MAPPER_ARMOURY) { + if (param_2 != Commands::M_MAPPER_TRADEPOST && param_2 != Commands::M_MAPPER_BARRACKS_EURO + && param_2 != Commands::M_MAPPER_BARRACKS_ARAB && param_2 != Commands::M_MAPPER_ARMOURY) { return param_2 != Commands::M_MAPPER_GRANARY; } } else { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index a9a7c19ff..d8cd9f8d1 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34851,7 +34851,7 @@ SHC_3BB0A8C1_0x004CB890 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB940 | 88.9% | Register allocation differs -SHC_3BB0A8C1_0x004CB9F0 | 93.3% | Reimplemented +SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CBA50 | 85.1% | Reimplemented functionally, stack and cached load differencesd From a6b3ea689de9d2307cf2e8067663111ecb0afb99 Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 19:50:22 +0200 Subject: [PATCH 009/104] reimplement: SHC_3BB0A8C1_0x004CC8D0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp index e71dc1e49..393f1a259 100644 --- a/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp +++ b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp @@ -11,7 +11,7 @@ namespace AI { using OpenSHC::Game::Skirmish::SkirmishDefinedData; // FUNCTION: STRONGHOLDCRUSADER 0x004CC8D0 - undefined4 AICState::unitIDIsRangedOrArmored(int unitID) + BOOLEnum AICState::unitIDIsRangedOrArmored(int unitID) { int _unitType = DAT_UnitsState::instance.units[unitID].unitType; From c33b3f1cb4d5ee0f52c7936943e599cec4a357cf Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 20:30:43 +0200 Subject: [PATCH 010/104] reimplement: SHC_3BB0A8C1_0x004D2000 32.00% loop bound fixed, asm differs --- src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp | 3 +-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index 7e51f30d5..340da5e9b 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -43,8 +43,7 @@ namespace AI { if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) return; - // The original also processes index 4, one past the end of SiegeEngineMetaInfoArray. - for (int siegeIndex = 0; siegeIndex < 5; siegeIndex++) { + for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { UnitType unitType = (UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index d8cd9f8d1..d1cc5c1b9 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 29.4% | Register allocation differs +SHC_3BB0A8C1_0x004D2000 | 32.0% | loop bound fixed, asm differs SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From a84adc12b7c535c901c86c5dbf3cd74aac0463ba Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 22:42:37 +0200 Subject: [PATCH 011/104] reimplement: SHC_3BB0A8C1_0x004CB940 100.00% Reimplemented --- src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp | 3 ++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp index e041584c2..afa234ad5 100644 --- a/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp +++ b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp @@ -34,7 +34,8 @@ namespace AI { return 0; MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( - DAT_BuildingsState::instance.buildings[buildingID].x, DAT_BuildingsState::instance.buildings[buildingID].y); + (short)DAT_BuildingsState::instance.buildings[buildingID].x, + (short)DAT_BuildingsState::instance.buildings[buildingID].y); MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, DAT_BuildingsState::ptr)(buildingID, owner, 50); MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index d1cc5c1b9..3e9fb8557 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34849,7 +34849,7 @@ SHC_3BB0A8C1_0x004CB810 | 92.3% | Reimplemented SHC_3BB0A8C1_0x004CB890 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB940 | 88.9% | Register allocation differs +SHC_3BB0A8C1_0x004CB940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented From bab6c5a5f0c425407c7e1fc3478cbe013231f9ae Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 22:47:11 +0200 Subject: [PATCH 012/104] reimplement: SHC_3BB0A8C1_0x004CE930 91.25% regalloc differs --- .../AI/AICState/findAttackTribePositionAndPathToDefenses.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp index b854d8312..db2abfa65 100644 --- a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp +++ b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp @@ -48,7 +48,7 @@ namespace AI { if (tile <= 0) return; - short tileY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int tileY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; DAT_GameState::instance.playerDataArray[playerID].someTile = tile; DAT_GameState::instance.playerDataArray[playerID].someY = tileY; DAT_GameState::instance.playerDataArray[playerID].someX diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 3e9fb8557..3a93e170a 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35333,7 +35333,7 @@ SHC_3BB0A8C1_0x004CE860 | 0.0% | Pending SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE930 | 87.0% | Register allocation differs +SHC_3BB0A8C1_0x004CE930 | 91.2% | regalloc differs SHC_3BB0A8C1_0x004CEA50 | 92.7% | Reimplemented functionally, stack ordering problem From fd38a27f2036dc1203c4ffceead85d67cd5cd47a Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 22:47:12 +0200 Subject: [PATCH 013/104] reimplement: SHC_3BB0A8C1_0x004CC420 39.42% regalloc/locals differ --- .../AI/AICState/generateSiegeCreationInformation.cpp | 8 ++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp index 95a84c59f..8bdf66eef 100644 --- a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp +++ b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp @@ -33,10 +33,10 @@ namespace AI { int i; for (i = 0; i < locationCount; i++) { if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i] > 0 - && DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] - .buildingID - == buildingID) + && buildingID + == DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID) break; } if (i >= locationCount) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 3a93e170a..ce3f51c5e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34867,7 +34867,7 @@ SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC420 | 38.0% | Loop strength reduction diff +SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented From 11b2e1f0e24420a21a7a7a18e2812bd5dcfc0172 Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 22:55:46 +0200 Subject: [PATCH 014/104] reimplement: SHC_3BB0A8C1_0x004D3AE0 36.88% stack layout now matches --- src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 6 +++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp index 0bcda0b49..d2bcde2c0 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -76,7 +76,7 @@ namespace AI { return; } - bool recruitDiggingUnit = false; + int recruitDiggingUnit = 0; if (this->aics[aicIndex].DefDiggingUnitMax != 0 && MACRO_CALL_MEMBER( OpenSHC::Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)(playerID) @@ -86,14 +86,14 @@ namespace AI { || DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10] != DAT_TribesState::instance.tribes[diggerTribeID].uid || DAT_TribesState::instance.tribes[diggerTribeID].size < this->aics[aicIndex].DefDiggingUnitMax) - recruitDiggingUnit = true; + recruitDiggingUnit = 1; } UnitType unitType = (UnitType)0; int behaviourType = 0; for (int i = 0; i < recruitCount; i++) { if (recruitDiggingUnit && (SEC_RNG::instance.currentNumber2 & 1) != 0) - recruitDiggingUnit = false; + recruitDiggingUnit = 0; if (recruitDiggingUnit) { if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index ce3f51c5e..7407f6f33 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35527,7 +35527,7 @@ SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 35.8% | Block layout differs +SHC_3BB0A8C1_0x004D3AE0 | 36.9% | stack layout now matches SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented From f561383156c3fc852262c70367284eca37a8c16d Mon Sep 17 00:00:00 2001 From: Gynt Date: Thu, 1 Oct 2026 23:59:42 +0200 Subject: [PATCH 015/104] AICState: record the measured cost of the smallestTribeOfUnitType shared tail The existing FIXME explains why the assignment is duplicated instead of using a goto. Rewriting it as a break into the shared tail at the end of the function, the way addUnitToSmallestBehaviourTypeTribe is written, looks like the obvious cleanup but drops the match from 40.9% to 17.0%, so say so next to the FIXME. Co-Authored-By: Claude Opus 5 --- src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp index f5c367225..c626ef572 100644 --- a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -34,7 +34,8 @@ namespace AI { // FIXME: The original binary jumps from here directly into the shared assignment code at the // end of the function, skipping the "smallestTribeID == 0" check. A goto reproduces this and // matches the assembly much better (~55% vs ~41%), but we avoid goto, so the assignment is - // duplicated here. + // duplicated here. Do not "simplify" this into a break into the shared tail below, the way + // addUnitToSmallestBehaviourTypeTribe is written: that was measured at 17%. tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)( playerID); DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i] = (short)tribeID; From c632c427da1c4cd4c36b4ff4d091e2fa5382595e Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:04:20 +0200 Subject: [PATCH 016/104] AICState: write setInitialAIC's qualified name without the stray space It was the only definition in the namespace written as "AICState ::setInitialAIC", which is valid C++ but makes the method invisible to anything grepping for "AICState::" - a method census read it as undefined. No change to the generated code. Co-Authored-By: Claude Opus 5 --- src/OpenSHC/AI/AICState/setInitialAIC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSHC/AI/AICState/setInitialAIC.cpp b/src/OpenSHC/AI/AICState/setInitialAIC.cpp index 0f1970d6d..9ab0b2c85 100644 --- a/src/OpenSHC/AI/AICState/setInitialAIC.cpp +++ b/src/OpenSHC/AI/AICState/setInitialAIC.cpp @@ -4,7 +4,7 @@ namespace OpenSHC { namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D18B0 - void AICState ::setInitialAIC() + void AICState::setInitialAIC() { MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::wipeAICMemory, this)(); From 92885461fe806a2b1f925958a13df91638ed1b43 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:34 +0200 Subject: [PATCH 017/104] tools/batch: commit_progress_batch committed whatever else was staged The per-function commit ran a bare "git commit", so any path already in the index was swept into the first function's commit. Importing files with "git checkout other-branch -- path" stages them, which put nine new sources into one commit whose message named a single function at 100%, and left the remaining five commits carrying only status-file churn. Restrict the commit to the source and the status file. Co-Authored-By: Claude Opus 5 --- tools/reimplementation-helper/batch/commit_progress_batch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/reimplementation-helper/batch/commit_progress_batch.py b/tools/reimplementation-helper/batch/commit_progress_batch.py index 241123951..c17a43e53 100644 --- a/tools/reimplementation-helper/batch/commit_progress_batch.py +++ b/tools/reimplementation-helper/batch/commit_progress_batch.py @@ -58,7 +58,10 @@ def commit(cpp, address, percent, remark): return False, "address %s not in %s (wrong FUNCTION address?)" % (address, STATUS_FILE) STATUS_FILE.write_text("".join(lines), encoding="utf-8", newline="") git("add", cpp, str(STATUS_FILE)) - r = git("commit", "-m", "reimplement: %s %.2f%% %s" % (function_id, percent, remark)) + # commit only these two paths: anything else already in the index (a `git checkout other-branch --` + # stages what it writes) would otherwise be swept into the first function's commit + r = git("commit", "-m", "reimplement: %s %.2f%% %s" % (function_id, percent, remark), + "--only", "--", cpp, str(STATUS_FILE)) return r.returncode == 0, r.stdout[-200:] + r.stderr[-300:] From dd20c8ad5b805813b0b1a695c88785cb14febbc0 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:45 +0200 Subject: [PATCH 018/104] reimplement: SHC_3BB0A8C1_0x004D0F40 100.00% Reimplemented --- .../playWillAttackBikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ status/addresses-SHC-3BB0A8C1.txt | 12 ++++----- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..6898bcfdf --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0F40 + void AICState::playWillAttackBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x21); + } +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7407f6f33..95e917090 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35435,17 +35435,17 @@ SHC_3BB0A8C1_0x004D0CF0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0D40 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0DB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0DB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E50 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0F40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F40 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0F90 | 100.0% | Reimplemented From 3ca8824a9c11e9a537cb255e819fdb400bb1861b Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:45 +0200 Subject: [PATCH 019/104] reimplement: SHC_3BB0A8C1_0x004D0E50 100.00% Reimplemented --- .../playWillHelpBikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..1cca496c9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0E50 + void AICState::playWillHelpBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x20); + } +} +} From 77bd87afc09330c3821e9c490240f0e4e1feb44c Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:46 +0200 Subject: [PATCH 020/104] reimplement: SHC_3BB0A8C1_0x004D0EA0 100.00% Reimplemented --- ...layWillNotAttack1BikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..a89b2faeb --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0EA0 + void AICState::playWillNotAttack1BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x18); + } +} +} From 2e8918955988750952f05240ec43e5349bc67f1c Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:46 +0200 Subject: [PATCH 021/104] reimplement: SHC_3BB0A8C1_0x004D0EF0 100.00% Reimplemented --- ...layWillNotAttack2BikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..7de2373fd --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0EF0 + void AICState::playWillNotAttack2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x19); + } +} +} From b7d598c008eba1c7996d83ef581049520b07e952 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:47 +0200 Subject: [PATCH 022/104] reimplement: SHC_3BB0A8C1_0x004D0DB0 100.00% Reimplemented --- .../playWillNotHelp1BikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..5c7ab00f7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0DB0 + void AICState::playWillNotHelp1BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x1a); + } +} +} From 2b888a0ad6146c0dba382830c08ebed6b47ab1ed Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 00:53:47 +0200 Subject: [PATCH 023/104] reimplement: SHC_3BB0A8C1_0x004D0E00 100.00% Reimplemented --- .../playWillNotHelp2BikFromPlayerToPlayer.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp diff --git a/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..164826d3f --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0E00 + void AICState::playWillNotHelp2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x1b); + } +} +} From 633c777acf60930d54e87dbebcb893570a418607 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 01:17:45 +0200 Subject: [PATCH 024/104] reimplement: SHC_3BB0A8C1_0x004CD070 100.00% Reimplemented --- .../checkTribeActivityPercentages.cpp | 25 +++++++++++++++++++ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp diff --git a/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp b/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp new file mode 100644 index 000000000..8bf0b8883 --- /dev/null +++ b/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD070 + BOOLEnum AICState::checkTribeActivityPercentages(int tribeID, BOOLEnum ignoreShooting, BOOLEnum includeMoving) + { + if (includeMoving != FALSE && DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk > 10) { + return TRUE; + } + if (DAT_TribesState::instance.tribes[tribeID].percentageShootingUnk > 10 && ignoreShooting != TRUE) { + return TRUE; + } + return DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10; + } + +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 95e917090..db8f4c686 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35263,7 +35263,7 @@ SHC_3BB0A8C1_0x004CCFA1 | 0.0% | Pending SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CD070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD070 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CD0C0 | 100.0% | Reimplemented From 0f4a8bd1d4ff73f0758c5061000a10c11d2fe03c Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 01:17:45 +0200 Subject: [PATCH 025/104] reimplement: SHC_3BB0A8C1_0x004CCFB0 90.00% imported, regalloc differs --- ...GenerateAITribesForPlayerIfNotExisting.cpp | 42 +++++++++++++++++++ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp diff --git a/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp new file mode 100644 index 000000000..d44cd5889 --- /dev/null +++ b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp @@ -0,0 +1,42 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCFB0 + int AICState::checksAndGenerateAITribesForPlayerIfNotExisting(int playerID, int maxAmount, BOOLEnum checkOnly) + { + int baseOffset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[1]; + for (int offset = 0; offset < maxAmount; offset++) { + if (offset >= 10) { + return 0; + } + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[baseOffset + offset]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[baseOffset + offset]) { + if (checkOnly != FALSE) { + return 1; + } + int newTribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[baseOffset + offset] = (short)newTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[baseOffset + offset] + = DAT_TribesState::instance.tribes[newTribeID].uid; + return newTribeID; + } + } + return 0; + } + +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index db8f4c686..496bc1fdc 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35261,7 +35261,7 @@ SHC_3BB0A8C1_0x004CCFA0 | 0.0% | Pending SHC_3BB0A8C1_0x004CCFA1 | 0.0% | Pending -SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCFB0 | 90.0% | imported, regalloc differs SHC_3BB0A8C1_0x004CD070 | 100.0% | Reimplemented From 25d846463793f2df1fb660acc6f93c36b6d7c352 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 01:17:45 +0200 Subject: [PATCH 026/104] reimplement: SHC_3BB0A8C1_0x004CC390 63.16% imported, asm differs --- .../AI/AICState/getUnitTypeIndexForUnitID.cpp | 47 +++++++++++++++++++ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp diff --git a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp new file mode 100644 index 000000000..5d9f0a8fb --- /dev/null +++ b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/Map/Units/UnitTypeInt.hpp" +#include "OpenSHC/Map/Units/UnitTypeShort.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::Map::Units::UnitTypeInt; + using OpenSHC::Map::Units::UnitTypeShort; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC390 + AIVUnitType AICState::getUnitTypeIndexForUnitID(int unitID, int param_2) + { + UnitTypeShort _unitType = DAT_UnitsState::instance.units[unitID].unitType; + short _playerID = DAT_UnitsState::instance.units[unitID].owner; + if (_unitType == OpenSHC::Map::Units::UT_PEASANT) { + _unitType = DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto; + } + UnitTypeInt _unitType2 = (UnitTypeInt)(short)_unitType; + + // 6 is the Caliph's AIC index: he fields slaves rather than the ranged units listed below + if (DAT_GameState::instance.playerDataArray[_playerID].aiType - 1 != 6 && param_2 != 0 + && DAT_GameState::instance.playerDataArray[_playerID].aivUnitLocationSlotLocationCount[0xd] > 0 + && (_unitType2 == OpenSHC::Map::Units::UT_E_ARCHER || _unitType2 == OpenSHC::Map::Units::UT_E_XBOW + || _unitType2 == OpenSHC::Map::Units::UT_A_ARCHER || _unitType2 == OpenSHC::Map::Units::UT_A_SLINGER + || _unitType2 == OpenSHC::Map::Units::UT_A_FIRETHROWER)) { + return OpenSHC::AI::AIVUT_SLAVE; + } + + for (int someUnitTypeIndex = 0; someUnitTypeIndex < 0x14; someUnitTypeIndex++) { + if (DAT_SkirmishDefinedData::instance.SomeAIUnitTypeArray[someUnitTypeIndex] == _unitType2) { + return (AIVUnitType)someUnitTypeIndex; + } + } + return OpenSHC::AI::AIVUT_NONE; + } + +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 496bc1fdc..66f5a771b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34865,7 +34865,7 @@ SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC390 | 63.2% | imported, asm differs SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ From d7414b00a195027836816fe6d1b54e66f0aff403 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 01:39:43 +0200 Subject: [PATCH 027/104] reimplement: SHC_3BB0A8C1_0x004CC390 79.49% block layout differs --- src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp | 8 +++++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp index 5d9f0a8fb..b1e67d353 100644 --- a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp +++ b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp @@ -20,11 +20,13 @@ namespace AI { AIVUnitType AICState::getUnitTypeIndexForUnitID(int unitID, int param_2) { UnitTypeShort _unitType = DAT_UnitsState::instance.units[unitID].unitType; - short _playerID = DAT_UnitsState::instance.units[unitID].owner; + int _playerID = DAT_UnitsState::instance.units[unitID].owner; + UnitTypeInt _unitType2; if (_unitType == OpenSHC::Map::Units::UT_PEASANT) { - _unitType = DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto; + _unitType2 = DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto; + } else { + _unitType2 = _unitType; } - UnitTypeInt _unitType2 = (UnitTypeInt)(short)_unitType; // 6 is the Caliph's AIC index: he fields slaves rather than the ranged units listed below if (DAT_GameState::instance.playerDataArray[_playerID].aiType - 1 != 6 && param_2 != 0 diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 66f5a771b..859f40f3b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34865,7 +34865,7 @@ SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC390 | 63.2% | imported, asm differs +SHC_3BB0A8C1_0x004CC390 | 79.5% | block layout differs SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ From b1e26bcd6c34c4ead6fdba39858f312b4ce9c091 Mon Sep 17 00:00:00 2001 From: Gynt Date: Fri, 2 Oct 2026 10:29:25 +0200 Subject: [PATCH 028/104] reimplement: SHC_3BB0A8C1_0x004CC390 100.00% Reimplemented Splitting the five ranged-unit checks out of the combined || condition into separate if statements with identical bodies takes getUnitTypeIndexForUnitID from 79.49% to an exact match: MSVC merges the five returns into one block, so the first comparison falls through into it and the other four jump back to it, which is what the original does. A switch over the same five cases measures 39.10%, so it is not a switch. Also records 0x004CCFB0 as matching: its remaining difference was the order of the two index additions, which comes from the inactive struct resolver rather than from the source, and it matches exactly once the reimplemented global data is enabled. Co-Authored-By: Claude Opus 5 --- .../AI/AICState/getUnitTypeIndexForUnitID.cpp | 21 ++++++++++++++----- status/addresses-SHC-3BB0A8C1.txt | 4 ++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp index b1e67d353..1c29a67df 100644 --- a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp +++ b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp @@ -30,11 +30,22 @@ namespace AI { // 6 is the Caliph's AIC index: he fields slaves rather than the ranged units listed below if (DAT_GameState::instance.playerDataArray[_playerID].aiType - 1 != 6 && param_2 != 0 - && DAT_GameState::instance.playerDataArray[_playerID].aivUnitLocationSlotLocationCount[0xd] > 0 - && (_unitType2 == OpenSHC::Map::Units::UT_E_ARCHER || _unitType2 == OpenSHC::Map::Units::UT_E_XBOW - || _unitType2 == OpenSHC::Map::Units::UT_A_ARCHER || _unitType2 == OpenSHC::Map::Units::UT_A_SLINGER - || _unitType2 == OpenSHC::Map::Units::UT_A_FIRETHROWER)) { - return OpenSHC::AI::AIVUT_SLAVE; + && DAT_GameState::instance.playerDataArray[_playerID].aivUnitLocationSlotLocationCount[0xd] > 0) { + if (_unitType2 == OpenSHC::Map::Units::UT_E_ARCHER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_E_XBOW) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_ARCHER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_SLINGER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_FIRETHROWER) { + return OpenSHC::AI::AIVUT_SLAVE; + } } for (int someUnitTypeIndex = 0; someUnitTypeIndex < 0x14; someUnitTypeIndex++) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 859f40f3b..7f1a7f81b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34865,7 +34865,7 @@ SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC390 | 79.5% | block layout differs +SHC_3BB0A8C1_0x004CC390 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ @@ -35261,7 +35261,7 @@ SHC_3BB0A8C1_0x004CCFA0 | 0.0% | Pending SHC_3BB0A8C1_0x004CCFA1 | 0.0% | Pending -SHC_3BB0A8C1_0x004CCFB0 | 90.0% | imported, regalloc differs +SHC_3BB0A8C1_0x004CCFB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CD070 | 100.0% | Reimplemented From c3e4afbad30936639e80743c9471c6e3e7caf0e6 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sat, 3 Oct 2026 23:07:23 +0200 Subject: [PATCH 029/104] reimplement: SHC_3BB0A8C1_0x004CFCD0 92.65% regalloc/addressing differs --- .../AI/AICState/aiCommandSiegeEngineTribes.cpp | 15 ++++++++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp index 8032d413f..d46970659 100644 --- a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -51,16 +51,18 @@ namespace AI { if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) continue; - int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; - if (tribeID == 0) - continue; - if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) != 0) MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( tribeID, STBT_0x414); } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.gateValuesArray + playerID * 0x177bc - 4) != 0) MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( @@ -69,7 +71,10 @@ namespace AI { != 0) MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( tribeID, STBT_0x413); - } else { + } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_SHIELD) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; if (shieldCount >= 3) continue; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7f1a7f81b..c8308c57f 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35387,7 +35387,7 @@ SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 56.0% | Unresolved attackInfo offsets +SHC_3BB0A8C1_0x004CFCD0 | 92.7% | regalloc/addressing differs SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented From d19d189b4c6855f0771df24e4e705e3c5b6e2426 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sat, 3 Oct 2026 23:54:37 +0200 Subject: [PATCH 030/104] reimplement: SHC_3BB0A8C1_0x004D3AE0 40.27% pointer-walk/regalloc differs --- src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 12 +++++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp index d2bcde2c0..46b4dba99 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -32,9 +32,11 @@ namespace AI { return; int aicIndex = aiType - 1; - bool isExtremeTrail = DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER + bool isExtremeTrail = false; + if (DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER && DAT_GameCore::instance.isSkirmishTrail == TRUE - && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME; + && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME) + isExtremeTrail = true; int recruitInterval = (&this->aics[aicIndex] .RecruitInterval)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; @@ -42,12 +44,12 @@ namespace AI { if (isExtremeTrail) recruitCount++; - if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + recruitCount = 4; + } else { DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker++; if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker < recruitInterval) return; - } else { - recruitCount = 4; } DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker = 0; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c8308c57f..a4b53689d 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35527,7 +35527,7 @@ SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 36.9% | stack layout now matches +SHC_3BB0A8C1_0x004D3AE0 | 40.3% | pointer-walk/regalloc differs SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented From 27bad64fc2c982af47dfec1f19a648bc3bd026b6 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 4 Oct 2026 00:35:23 +0200 Subject: [PATCH 031/104] cheat sheet: record three measured AI::AICState diff patterns - a field read every arm of an if/else-if chain needs is read inside each arm in the original, not hoisted above it (+33 points on aiCommandSiegeEngineTribes) - the smallest-tribe family's shared store-and-return tail has no goto-free form that is right for all of them; the three measured ones disagree, so measure each - the original's zero-register memory comparisons are allocator noise, not /Os: compiling three such functions with /O2 /Os left them at exactly their old scores Co-Authored-By: Claude Opus 5 --- IMPLEMENTATION_CHEAT_SHEET.md | 103 ++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 29 deletions(-) diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index 97b2e5cf8..e57d838fd 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -8,7 +8,7 @@ Note, that Ghidra and reccmp use slightly different disassembly styles, so there ## Boundaries -Certain instructions work as boundaries between different code segments, preventing reordering of instructions and aiding in identifying the possible structure. +Certain instructions work as boundaries between different code segments, preventing reordering of instructions and aiding in identifying the possible structure. ### Functions @@ -39,6 +39,7 @@ it is a clean indication of a known function body. The source of the other funct allowing the compiler to optimize the register usage. #### Detecting inlined functions + The compiler (and/or linker) likely inlined some functions. This mainly happens if the compilation unit (in our case function body) can "see" the contents of another compilation unit (function body). Detecting whether this has occurred is challenging. So far, we have found one pattern that predicts it (occurs in OpenSHC::Global::WinMain). @@ -89,6 +90,7 @@ which usually also cascades in further code changes. ### Conditionals Instructions can only be pulled: + - before a condition block if every block has the same instruction at the start. - after a condition block if every block that does not return has the same instruction at the end. @@ -106,7 +108,7 @@ Within a boundary the compiler schedules freely, so do not copy the instruction This matters most for long runs of independent stores into one object, as in `AICState::setAICParameters_NN`: the whole body is `this->aics[aicIndex]. = ;` over ~130 fields, and the compiler emits those `mov dword ptr [eax + ], ` instructions in an order of its own. Rewriting all 16 of -those functions so their statements followed the original's store order made every one of them *worse* +those functions so their statements followed the original's store order made every one of them _worse_ (e.g. `setAICParameters_14` 82.4% -> 50.0%). That does not make the order unrecoverable. The compiler moves only the stores whose value sits in a **register**: @@ -123,7 +125,7 @@ are pulled up in front of the reload, and stores of a newly loaded constant are 3. Compile and move the few statements that still sit elsewhere next to their neighbour in the original. All 16 `setAICParameters_NN` went from 75-95% to 100% this way, with no type or header change; the earlier attempt -failed because it copied the *instruction* order, hoisted stores included. Which constant lands in which register +failed because it copied the _instruction_ order, hoisted stores included. Which constant lands in which register then follows by itself - it was a consequence of the statement order, not of register pressure. ## Structure @@ -135,6 +137,7 @@ at the usage of a structure that the compiler optimized. If not contained in the examples here yet, AI is usually good at identifying them. The most prominent example are modulo operations so far: + ```cpp if ((int)SEC_RNG.currentNumber1 % 3 == 0) { DAT_SoundEffectsHelperData1.field6_0x34 = 1; @@ -143,7 +146,9 @@ else { DAT_SoundEffectsHelperData1.field6_0x34 = (-(uint)((int)SEC_RNG.currentNumber1 % 3 != 1) & 0xfffffffe) + 2; } ``` + is roughly: + ```cpp int const randomNumber = SEC_RNG::instance.currentNumber1 % 3; if (randomNumber == 0) @@ -155,13 +160,16 @@ else ``` Or another modulo: + ```cpp uVar1 = (int)SEC_RNG.currentNumber1 & 0x80000003; if ((int)uVar1 < 0) { uVar1 = (uVar1 - 1 | 0xfffffffc) + 1; } ``` + being + ```cpp SEC_RNG::ptr->currentNumber1 % 4 ``` @@ -196,7 +204,7 @@ SEC_RNG::ptr->currentNumber1 % 4 every use took `getTargetableBuildingForPlayerID` from 65% to 100%. - **A constant index held in a local stops the per-struct multiply being shared.** When the original recomputes `playerID * 0x39f4` in every basic block (`mov eax, ecx; imul eax, eax, 0x39f4` three times, with `playerID` staying - in its register) while we multiply once, the array index in the source was a *variable* that the optimizer only + in its register) while we multiply once, the array index in the source was a _variable_ that the optimizer only later found to be constant. With a variable index MSVC factors each access by its own element size (`playerID * 0x1cfa` for a `short` array, `* 0xe7d` for an `int` array), so the three accesses are three different expressions and nothing is common. Write `int tribeIndex = 11;` and index with it: @@ -220,7 +228,7 @@ SEC_RNG::ptr->currentNumber1 % 4 (`removeOrganismsAndSetMoveDestinationPairs` 97.9% -> 100%). - Where two arguments are loaded on either side of a store (`mov ecx, [x]` ... `mov [stance], 1` ... `mov eax, [y]`), give each a local and put the statements in that order: `int x = pd.someX; tribe.unitStance = ...; int y = pd.someY;` - (`makeUnitsGoDefensiveAndBackToSomeLocation` 95.7% -> 100%). Without a store in between, the *declaration order* + (`makeUnitsGoDefensiveAndBackToSomeLocation` 95.7% -> 100%). Without a store in between, the _declaration order_ of two such locals picks which one gets `ecx` (`sendUnitsToAttackBreachedCastle` 98.7% -> 100%). - The opposite does happen for the operands of a single statement: in `SHC_3BB0A8C1_0x00522520` the original evaluates `unitID / 16` and `unitID % 16` into locals before the compound assignment. If a `|=`/`&=` statement with computed index @@ -244,6 +252,13 @@ SEC_RNG::ptr->currentNumber1 % 4 `if ((x & MASK) == VALUE) { return TRUE; } else { return FALSE; }`, with locals for the computed index and the loaded byte, reached 100%. Together with the range-check case above this means: folded expression and explicit `TRUE`/`FALSE` branches are two separate candidates, measure both. +- A field read that every arm of an if/else-if chain needs is often read **inside each arm** in the original, not + hoisted above the chain. When the original shows the same load repeated once per arm (three `movsx [ebx + 0x24a]` + for one `unit.tribeID`, each followed by its own `test`/`jz` to the loop's continue trampoline), move the read and + its guard into every arm instead of declaring one local before the chain. This is the same duplicate-the-shared- + block rule as for `LAB_*` targets with several predecessors, applied to a read rather than a block, and it was + worth +33 points on its own (SHC_3BB0A8C1_0x004CFCD0 `aiCommandSiegeEngineTribes`, 59.4% -> 92.7%). Spell the last + arm's condition out as `else if (type == UT_S_SHIELD)` too when the assembly tests it, rather than a bare `else`. - A switch with fewer then 4 cases is often simplified and uses subtractions in assembly to compare the value: ``` MOV EAX,[DAT_SoundEffectsHelperData1.SEC_Section1079. volumeLevel] @@ -255,7 +270,7 @@ SEC_RNG::ptr->currentNumber1 % 4 JNZ LAB_0047c386 ``` Once 4 are reached, the switch is probably converted to a table lookup. - This structure shows up as if-else chain in the decompiler, so do not be confused and check the assembly if the conditions are repeated checks of the same value. + This structure shows up as if-else chain in the decompiler, so do not be confused and check the assembly if the conditions are repeated checks of the same value. - A switch is in the end just a structure with a scope that allows to jump to certain cases. Inside this scope, other structures are fully legal. This is allowed, for example: ```cpp switch(value) { @@ -284,6 +299,7 @@ SEC_RNG::ptr->currentNumber1 % 4 Loops are very often presented in the decompiler as a pointer being incremented or decremented. The pointer is often created by getting the address to an array before the loop. There is also often a condition variable present that is incremented or decremented with the direction not necessarily matching the direction of the pointer increment. Example: + ```cpp int _soundIndex = 1; if (1 < DAT_SoundSystemState.loadedSoundsCountAndIndex_0x316c) { @@ -300,6 +316,7 @@ if (1 < DAT_SoundSystemState.loadedSoundsCountAndIndex_0x316c) { ``` It should always be attempted to simplify this logic to a default for-block with array index access: + ```cpp for (int soundIndex = 1; soundIndex < this->loadedSoundsCountAndIndex_0x316c; ++soundIndex) { if (this->soundFileCurrSampleNum_0x28c[soundIndex] < 0 || !this->samplePaused_0x31f4[soundIndex]) { @@ -313,17 +330,18 @@ for (int soundIndex = 1; soundIndex < this->loadedSoundsCountAndIndex_0x316c; ++ This covers a lot of cases. Exceptions are: - - when the logic has not initial condition check before the loop body. This suggests the usage of a do-while loop. - - when the update parts of the pointer and index are not the last instructions before the loop condition. This indicates manual handling like in a while loop. - - when it might lack a counter variable and the index loop simply does not match. This case might indicate a pointer increment loop. - Another sign for this could be the storing of the start of an array before the array is incremented. The main pointer is then often reset using this temporary. This may should up in opcodes like this: - ``` - MOV EDX, dword ptr [ESP + 0x20] <-- - MOV dword ptr [ESI + 0xc], EBX - MOV dword ptr [ESI + 0x24], EBX - MOV dword ptr [ESP + 0x20], EDX <-- - ``` - Note the seemingly redundant storing. This structure is followed by a loop where the main array is incremented and a temp used to reset it. + +- when the logic has not initial condition check before the loop body. This suggests the usage of a do-while loop. +- when the update parts of the pointer and index are not the last instructions before the loop condition. This indicates manual handling like in a while loop. +- when it might lack a counter variable and the index loop simply does not match. This case might indicate a pointer increment loop. + Another sign for this could be the storing of the start of an array before the array is incremented. The main pointer is then often reset using this temporary. This may should up in opcodes like this: + ``` + MOV EDX, dword ptr [ESP + 0x20] <-- + MOV dword ptr [ESI + 0xc], EBX + MOV dword ptr [ESI + 0x24], EBX + MOV dword ptr [ESP + 0x20], EDX <-- + ``` + Note the seemingly redundant storing. This structure is followed by a loop where the main array is incremented and a temp used to reset it. If you see a repeating logic structure, that, for example, increments by a value in its logic every repeat, you might have found an unrolled loop. Therefore, try to reproduce the logic in loop form and see how the compiler behaves. @@ -340,7 +358,7 @@ worth the last 8-10% on the whole `Map::Version::UpgradeMapUnitsTo_*` family (0x unrolled body, write `for (int i = 1; i != 2500; ++i)` instead of `i < 2500` (SHC_3BB0A8C1_0x0053B340, 53% -> 91%). - A loop whose early exit returns the same value as the code after the loop was a `break`, not a `return`. The tell is - callee-saved registers pushed *after* the loop's entry test and one shared `mov eax, result` behind the loop, where + callee-saved registers pushed _after_ the loop's entry test and one shared `mov eax, result` behind the loop, where an early `return result;` makes us push everything in the prologue (`assignRequiredIdleEngineersToNewTribe`, `addEngineersToSelection`, both ~88% -> 100%). - Do not add a guard the loop condition already expresses. `if (id == 0) return 0;` in front of @@ -350,27 +368,50 @@ unrolled body, write `for (int i = 1; i != 2500; ++i)` instead of `i < 2500` (SH - **Several exits that end in the same statement were usually one statement.** MSVC copies a short shared tail (a store or a call followed by the epilogue) into each predecessor, so three `mov word ptr [tribe.unitStance], 2` - + `ret` blocks in the original do not mean three assignments in the source. Writing the assignment once after an - `if / else if` ladder instead of `stance = X; return;` in every arm changes how often each value is *used*, and the - use count is what ranks values for the callee-saved registers - so this is the fix when a function matches - instruction for instruction except that two registers are swapped (`tribeID` in `ebx` and the tribe offset in `ebp` - on one side, the reverse on the other). `giveMoveCommandToSortieUnits` 79.6% -> 100%, `instructTribe166ToMove` - 81.8% -> 100%, `assignUnitToATribe` 86.1% -> 100% (one `addUnitToTribe(unitID, tribeID)` after the branches - instead of three calls). When one of the arms must skip a call the others share, a `bool` set in the arms and - tested once afterwards compiles away and gives the single call site the original has. + - `ret` blocks in the original do not mean three assignments in the source. Writing the assignment once after an + `if / else if` ladder instead of `stance = X; return;` in every arm changes how often each value is _used_, and the + use count is what ranks values for the callee-saved registers - so this is the fix when a function matches + instruction for instruction except that two registers are swapped (`tribeID` in `ebx` and the tribe offset in `ebp` + on one side, the reverse on the other). `giveMoveCommandToSortieUnits` 79.6% -> 100%, `instructTribe166ToMove` + 81.8% -> 100%, `assignUnitToATribe` 86.1% -> 100% (one `addUnitToTribe(unitID, tribeID)` after the branches + instead of three calls). When one of the arms must skip a call the others share, a `bool` set in the arms and + tested once afterwards compiles away and gives the single call site the original has. - A search loop whose "found a free slot" exit must skip the check that follows the loop has its end test **inside the body**: `for (;; i++) { if (i >= count) { if (best == 0) return 0; break; } ... if (free) { best = create(); - bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it tests `best` on both paths, and +bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it tests `best` on both paths, and duplicating the tail into the create path changes the frame. With the test in the body the compiler rotates it to the bottom and lays the blocks out as the original does (`getSmallestPatrolTribe` 65.2% -> 100%). The same - function needed `int i = 0;` declared *first*, before the other zero-initialised locals, to get the original's + function needed `int i = 0;` declared _first_, before the other zero-initialised locals, to get the original's `xor` order - declaration order did not matter anywhere else it was tried. - `mov edx, esi; add esi, 1` at the **top** of a loop body, with the body then indexing by the copy, is a `while` loop that takes the index and advances the counter first: `while (i < count) { int index = i; i++; if (...[index]...) - { ...; return; } }`. A `for (...; i++)` with the same body does not produce it. Together with `int i = 0;` +{ ...; return; } }`. A `for (...; i++)` with the same body does not produce it. Together with `int i = 0;` declared where the original's `xor esi, esi` sits (here before an earlier early-return test, which then compares against that zero register) this took `generateSiegeCreationInformation` from 39.4% to 100%. +- Where the original compares a memory operand against a **zero register** (`xor ebp,ebp` once, then + `cmp dword ptr [..], ebp` and `mov dword ptr [..], ebp`) and we emit `test r,r` / `cmp ..,0`, that is **not** a + source-level difference and **not** a size-vs-speed flag. Using a zero register saves a byte per memory + comparison, so `/Os` looks like the explanation, but compiling with `/O2 /Os` per file (verified present in the + compile command) left `getTargetableBuildingForPlayerID`, `getSmallestPatrolTribe` and + `aiShouldAttackOrWaitForTeamCoordination` at _exactly_ their previous percentages. The choice cascades into a + different register assignment and sometimes one extra spill of the loop counter, which is why these functions + stall in the 43-65% band with an otherwise instruction-for-instruction match. Treat it as allocator noise and + record it; it shows up across `AI::AICState` (`aiRecruitUnits`, `aiGiveRaidInstructions`, + `generateSiegeCreationInformation`, `getTargetableBuildingForPlayerID`, `getSmallestPatrolTribe`). +- The "find the smallest tribe, or create one if a slot is stale" family all have the same shape: a loop whose + create-path wants to reach the function's shared store-and-return tail while skipping the tail's + `selected == 0` guard, which the original does with a jump into the middle of that block. There is no goto-free + form that is right for all of them, so **measure each one**; the three measured so far disagree: + - `getSmallestPatrolTribe` (0x004CCAF0): `break` into the shared tail is best at **65.2%**; duplicating the + stores into the create path drops it to 48.3%. + - `smallestTribeOfUnitType` (0x004CC990): the reverse - duplicating the stores gives **41.9%**, a `goto` into + the tail would give ~55%, and `break` into the shared tail was measured at 17%. + - `addUnitToSmallestBehaviourTypeTribe` (0x004CCD20): `break` into the shared tail, **64.5%**; neither hoisting + the `smallestSize` initialiser to the top (where the original's own `mov [esp+0x24],0x3e8` sits) nor reusing + the loop variable for the slot index moved it at all - both exact ties. + So the predecessor-count rule tells you a duplicate _may_ be needed, not that it will help; `try_styles.py` is + the only way to settle it. ### GOTO @@ -474,6 +515,7 @@ However, there are also C functions that can be replaced by intrinsics. The foll In cases where the decompiler seems to perform one of these actions via simple instructions, one can also try one of these functions. Example `strcpy`: + ```cpp char* pcVar2 = filePath; do { @@ -484,6 +526,7 @@ do { ``` Example `strcmp`: + ```cpp int iVar2 = 9; int bVar7 = true; @@ -518,11 +561,13 @@ The return value optimization may appear if a function returns an object, but in The actual function signature will only have the object as value return. Example in Ghidra: + ```cpp std::string* paths_getDocumentsFolderString(std::string* out, bool param_2); ``` Actual signature: + ```cpp std::string paths_getDocumentsFolderString(bool param_2); ``` From e3a6a67e0dd384d79ff63b3c1d24b7864b87794f Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 4 Oct 2026 00:57:01 +0200 Subject: [PATCH 032/104] tools/batch: document that the two norm columns differ, and that diff_triage skips functions reccmp_report.py's norm does not normalize registers, so an instruction-for-instruction match with two registers swapped reports norm == raw; only diff_triage.py's norm does. diff_triage.py also drops functions whose blocks it cannot pair - 12 of 68 in one AI::AICState run, and they were the lowest-scoring ones. Co-Authored-By: Claude Opus 5 --- tools/reimplementation-helper/batch/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/reimplementation-helper/batch/README.md b/tools/reimplementation-helper/batch/README.md index a5de287a6..6cfda14e2 100644 --- a/tools/reimplementation-helper/batch/README.md +++ b/tools/reimplementation-helper/batch/README.md @@ -73,6 +73,20 @@ python reccmp_report.py diff someFunction # inspe python commit_progress_batch.py Map/Units "someFunction=LTCG ecx reuse" ``` +## Which `norm` is which + +The two tools print a column called `norm` and they do **not** mean the same thing: + +- `reccmp_report.py`'s `norm` ignores call/tail-jump targets and resolver addresses only. It does **not** normalize + registers, so a function that matches the original instruction-for-instruction except that two registers are + swapped still reports `norm` equal to its raw percentage. Do not read a low `norm` here as "real structure differs". +- `diff_triage.py`'s `norm` does normalize registers and stack slots, and is the one that tells you whether source + work can still pay. + +`diff_triage.py` also silently **skips** functions whose blocks it cannot pair - 12 of 68 in one `AI::AICState` run, +and they were exactly the lowest-scoring ones. If a function you care about is missing from its table, fall back to +reading `reccmp_report.py diff NAME` and judging by eye whether the two streams differ only in register names. + ## Reading `diff_triage.py` The two ratios are the quickest way to tell whether more source work can pay: From 4a862db73cb0fb4ad1846dbbff72d3f905834c9e Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 4 Oct 2026 22:11:39 +0200 Subject: [PATCH 033/104] reimplement: SHC_3BB0A8C1_0x004D2000 34.88% fixed siegeIndex>=2 skip Behaviour fix found by reading the original: at 0x4d219f the original tests the siege index on the existingBuildingID != 0 path and jumps to the loop-continue block at 0x4d23a7 when it is >= 2. Our `else if (siegeIndex < 2)` fell through and proceeded instead, so siege indices 2 and 3 with a building already on the tile took the create path they should have skipped. Match is unchanged (34.88% before and after, measured); the remaining gap is the frame-size mismatch (sub esp 0x30 vs 0x2c) and register allocation. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Mue4Btj5KqeNDUyuFn2uLZ --- src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp | 4 +++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index 340da5e9b..d1cac0ab6 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -123,7 +123,9 @@ namespace AI { if (existingBuildingID == 0) { if (siegeIndex < 2) continue; - } else if (siegeIndex < 2) { + } else { + if (siegeIndex >= 2) + continue; if (DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType != OpenSHC::Map::Buildings::BT_TOWER4 && DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index a4b53689d..7ebea924c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 32.0% | loop bound fixed, asm differs +SHC_3BB0A8C1_0x004D2000 | 34.9% | fixed siegeIndex>=2 skip; fram SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From d8637dbc8a3f61f61416da5ce795ad16baa5e739 Mon Sep 17 00:00:00 2001 From: Gynt Date: Sun, 4 Oct 2026 22:36:59 +0200 Subject: [PATCH 034/104] cheat sheet: statement order is not the lever for the setAICParameters family reorder_search.py on setAICParameters_15 permuted all 9 independent runs over 27 builds and kept nothing - it stayed at exactly 74.92%. The family's gap is the order in which repeated constants are hoisted into registers, which the source cannot choose; every field store already matches. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Mue4Btj5KqeNDUyuFn2uLZ --- IMPLEMENTATION_CHEAT_SHEET.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index e57d838fd..074d8be07 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -389,6 +389,13 @@ bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it { ...; return; } }`. A `for (...; i++)` with the same body does not produce it. Together with `int i = 0;` declared where the original's `xor esi, esi` sits (here before an earlier early-return test, which then compares against that zero register) this took `generateSiegeCreationInformation` from 39.4% to 100%. +- The `setAICParameters_01..16` family (0x004C6D60 and up) is a long run of independent constant stores into one + `aics[aicIndex]` element, and it sits at 74-95% because the original hoists repeated constants into registers in a + particular order while we materialise them in another; every actual field store already matches, and the field + offsets check out. Statement order is **not** the lever: `reorder_search.py` on `setAICParameters_15` tried all 9 + independent runs (lengths 42, 29, 21, 14, 6, 4, 3, 3, 3) over 27 builds and kept nothing, leaving it at exactly + 74.92%. MSVC schedules those stores the same way whatever order the source lists them in, which is the + _Statement Order_ note above measured on a large case. Record the percentage and move on. - Where the original compares a memory operand against a **zero register** (`xor ebp,ebp` once, then `cmp dword ptr [..], ebp` and `mov dword ptr [..], ebp`) and we emit `test r,r` / `cmp ..,0`, that is **not** a source-level difference and **not** a size-vs-speed flag. Using a zero register saves a byte per memory From 64879721b45e097b377332e51d0d2e42c58de5e2 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:30 +0200 Subject: [PATCH 035/104] AICState: enable the resolvers aiBuyAndSellGoods tail-jumps to aiBuyAndSellGoods ends in a tail jump to sellExcessGoods or buyRequiredGoods. reccmp's --resolve-wrapped-calls resolves a call through a resolver wrapper but not a jmp to one, so the function sat at 86.7% (normalized 100%) until both callees are marked reimplemented and the jump goes to them directly. Co-Authored-By: Claude Fable 5.1 --- src/OpenSHC/AI/AICState.func.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState.func.hpp b/src/OpenSHC/AI/AICState.func.hpp index 42f3f8974..6ef9588e9 100644 --- a/src/OpenSHC/AI/AICState.func.hpp +++ b/src/OpenSHC/AI/AICState.func.hpp @@ -631,7 +631,7 @@ namespace AI { decideOnTribeAttackLocation; MACRO_FUNCTION_RESOLVER( - void (AICState::*)(int), false, Address::SHC_3BB0A8C1_0x004D1D60, &AICState::sellExcessGoods) + void (AICState::*)(int), true, Address::SHC_3BB0A8C1_0x004D1D60, &AICState::sellExcessGoods) sellExcessGoods; MACRO_FUNCTION_RESOLVER(void (AICState::*)(int, ResourceType, int), false, Address::SHC_3BB0A8C1_0x004D1F00, @@ -715,7 +715,7 @@ namespace AI { makeOutpostTribesAttack; MACRO_FUNCTION_RESOLVER( - void (AICState::*)(int), false, Address::SHC_3BB0A8C1_0x004D39B0, &AICState::buyRequiredGoods) + void (AICState::*)(int), true, Address::SHC_3BB0A8C1_0x004D39B0, &AICState::buyRequiredGoods) buyRequiredGoods; MACRO_FUNCTION_RESOLVER( From f2d3cc7887404858d1e5906b8ce517ed3c282f02 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:31 +0200 Subject: [PATCH 036/104] reimplement: SHC_3BB0A8C1_0x004CC680 100.00% Reimplemented --- src/OpenSHC/AI/AICState/addEngineersToSelection.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp index 3aae5e97a..ce614c098 100644 --- a/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp +++ b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp @@ -18,7 +18,7 @@ namespace AI { = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { if (unitCount <= 0) - return tribeID; + break; if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) continue; if (DAT_UnitsState::instance.units[unitID].owner != playerID) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7ebea924c..225f8604c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34873,7 +34873,7 @@ SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC5A0 | 85.7% | Register allocation differs -SHC_3BB0A8C1_0x004CC680 | 83.3% | Register push placement diff +SHC_3BB0A8C1_0x004CC680 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC6B0 | 0.0% | Pending From 8713a8d18e92f065e8ea70e4d9078f0cfcee3eac Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:31 +0200 Subject: [PATCH 037/104] reimplement: SHC_3BB0A8C1_0x004D2730 100.00% Reimplemented --- src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp | 6 +++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp index 40602a5c4..e92b28080 100644 --- a/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp @@ -14,13 +14,13 @@ namespace AI { void AICState::addUnitToSmallestPatrolTribe(int unitID) { int owner = DAT_UnitsState::instance.units[unitID].owner; - int aiType = DAT_GameState::instance.playerDataArray[owner].aiType; - if (aiType == AITA_NULL) + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) return; + int aicIndex = DAT_GameState::instance.playerDataArray[owner].aiType - 1; DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 4; int tribeID = MACRO_CALL_MEMBER(AICState_Func::getSmallestPatrolTribe, this)( - owner, this->aics[aiType - 1].OuterPatrolGroupsCount); + owner, this->aics[aicIndex].OuterPatrolGroupsCount); MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 225f8604c..1ec006ffc 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35489,7 +35489,7 @@ SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented SHC_3BB0A8C1_0x004D2660 | 59.6% | Branch layout differs -SHC_3BB0A8C1_0x004D2730 | 79.2% | AIC index folding differs +SHC_3BB0A8C1_0x004D2730 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D2790 | 91.3% | Reimplemented From 945c909bbf17d0286b6bb3b86363110660958a95 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:31 +0200 Subject: [PATCH 038/104] reimplement: SHC_3BB0A8C1_0x004CFCD0 99.36% reload order after call differ --- src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp | 6 +++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp index d46970659..3a7bf0c41 100644 --- a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -27,12 +27,12 @@ namespace AI { shieldTargetTribeIDs[1] = 0; shieldTargetTribeIDs[2] = 0; int targetCount = 0; - for (int i = 0; i < 3; i++) { - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[186 + i]; + for (int i = 186; i < 189; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[i]; if (tribeID == 0) continue; if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[186 + i]) + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i]) continue; shieldTargetTribeIDs[targetCount] = tribeID; targetCount++; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 1ec006ffc..2804c6d3a 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35387,7 +35387,7 @@ SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 92.7% | regalloc/addressing differs +SHC_3BB0A8C1_0x004CFCD0 | 99.4% | reload order after call differ SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented From febd8d47dd64f3b5de349ef88a6dfb490164ade0 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:32 +0200 Subject: [PATCH 039/104] reimplement: SHC_3BB0A8C1_0x004CF470 100.00% Reimplemented --- .../AI/AICState/aiCommandTribe11StandGroundToGate.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp index f4f46c614..265f1d75d 100644 --- a/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp @@ -13,13 +13,14 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF470 void AICState::aiCommandTribe11StandGroundToGate(int playerID) { + int tribeIndex = 11; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) return; - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) return; if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) return; if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) return; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 2804c6d3a..f8e2b8d3c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35359,7 +35359,7 @@ SHC_3BB0A8C1_0x004CF2E0 | 46.8% | Orig recomputes player offset SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations -SHC_3BB0A8C1_0x004CF470 | 56.0% | Orig recomputes player offset +SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF4F0 | 57.10% | Reimplemented functionally, our compiler improves on original by avoiding recomputations From 3311de98117808cc3a3be000da732503a29b7a57 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:32 +0200 Subject: [PATCH 040/104] reimplement: SHC_3BB0A8C1_0x004CF2E0 83.44% esi/edi regalloc differs --- .../AI/AICState/aiCommandTribe12AggressiveApproach.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp index b82c43b75..0c086b105 100644 --- a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -17,13 +17,14 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF2E0 void AICState::aiCommandTribe12AggressiveApproach(int playerID) { + int tribeIndex = 12; if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) return; - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) return; if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) return; int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; if (targetUnitID == 0) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f8e2b8d3c..6cec0964f 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35355,7 +35355,7 @@ SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF270 | 62.2% | Orig recomputes player offset -SHC_3BB0A8C1_0x004CF2E0 | 46.8% | Orig recomputes player offset +SHC_3BB0A8C1_0x004CF2E0 | 83.4% | esi/edi regalloc differs SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations From b96c093eb44c856ac0998ff5093d64c3b2985dca Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:32 +0200 Subject: [PATCH 041/104] reimplement: SHC_3BB0A8C1_0x004CF270 100.00% Reimplemented --- src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp index b921b42ae..fe8539329 100644 --- a/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp @@ -13,13 +13,14 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF270 void AICState::aiCommandTribe13DefensiveWave(int playerID) { + int tribeIndex = 13; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) return; - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[13]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) return; if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[13]) + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) return; if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) return; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6cec0964f..6cf9f5ba5 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35353,7 +35353,7 @@ SHC_3BB0A8C1_0x004CF060 | 0.0% | Pending SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF270 | 62.2% | Orig recomputes player offset +SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF2E0 | 83.4% | esi/edi regalloc differs From a326ba0ec6ca82cf3b5039923e7c044442d84dfb Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:33 +0200 Subject: [PATCH 042/104] reimplement: SHC_3BB0A8C1_0x004CB3A0 100.00% Reimplemented --- .../AI/AICState/aiRequiresExtraOxtethers.cpp | 14 ++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp index 7117f018c..799e96e0c 100644 --- a/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp +++ b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp @@ -15,17 +15,14 @@ namespace AI { int quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY); - int highestStonePerTether = 0; int highestQuarryID = 0; + int highestStonePerTether = 0; if (MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_OXTETHER, 1) >= 10) return 0; DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = 0; - if (quarryID == 0) - return 0; - for (; quarryID != 0; quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findNextBuildingForPlayerAndType, DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY, quarryID)) { @@ -55,10 +52,11 @@ namespace AI { } } - if (highestStonePerTether <= 20) - return 0; - DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = highestQuarryID; - return 1; + if (highestStonePerTether > 20) { + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = highestQuarryID; + return 1; + } + return 0; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6cf9f5ba5..bb2d6203e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34215,7 +34215,7 @@ SHC_3BB0A8C1_0x004CB290 | 98.1% | Reimplemented SHC_3BB0A8C1_0x004CB330 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB3A0 | 85.3% | Register allocation differs +SHC_3BB0A8C1_0x004CB3A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB4D0 | 97.5% | Reimplemented From 93c3b5840d17bca822272d445b6aa030c8b6ef2c Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:33 +0200 Subject: [PATCH 043/104] reimplement: SHC_3BB0A8C1_0x004CC5A0 100.00% Reimplemented --- .../AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp index d6f813898..2418ab3e9 100644 --- a/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp +++ b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp @@ -17,7 +17,7 @@ namespace AI { = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { if (count <= 0) - return tribeID; + break; if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) continue; if (DAT_UnitsState::instance.units[unitID].owner != playerID) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index bb2d6203e..4dc0a92f0 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34871,7 +34871,7 @@ SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC5A0 | 85.7% | Register allocation differs +SHC_3BB0A8C1_0x004CC5A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC680 | 100.0% | Reimplemented From d37d5fd09be74d83208c7513adcdf880227604df Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:34 +0200 Subject: [PATCH 044/104] reimplement: SHC_3BB0A8C1_0x004D2660 86.11% ebx/ebp regalloc differs --- src/OpenSHC/AI/AICState/assignUnitToATribe.cpp | 13 ++++++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp index 9f320a41b..42fac935e 100644 --- a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp +++ b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp @@ -17,11 +17,14 @@ namespace AI { if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) return; - AIVUnitType aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, - DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex == 1 - && DAT_GameState::instance.playerDataArray[owner].aivPauses[1] > 0 - ? 1 - : 0); + AIVUnitType aivUnitType; + if (DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[owner] + .aivPauses[DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex] + > 0) + aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, 1); + else + aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, 0); DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 1; if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlotLocationCount[aivUnitType] <= 0) { if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 4dc0a92f0..ca016fe0e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35487,7 +35487,7 @@ SHC_3BB0A8C1_0x004D20C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented -SHC_3BB0A8C1_0x004D2660 | 59.6% | Branch layout differs +SHC_3BB0A8C1_0x004D2660 | 86.1% | ebx/ebp regalloc differs SHC_3BB0A8C1_0x004D2730 | 100.0% | Reimplemented From 4a5d9fe01db409cfcb29cf7a834f3f6318c29229 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:34 +0200 Subject: [PATCH 045/104] reimplement: SHC_3BB0A8C1_0x004D3780 100.00% Reimplemented --- src/OpenSHC/AI/AICState/computeNervousness.cpp | 10 ++++------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/OpenSHC/AI/AICState/computeNervousness.cpp b/src/OpenSHC/AI/AICState/computeNervousness.cpp index 2d92d4bca..65e1fe7bc 100644 --- a/src/OpenSHC/AI/AICState/computeNervousness.cpp +++ b/src/OpenSHC/AI/AICState/computeNervousness.cpp @@ -14,6 +14,7 @@ namespace AI { = DAT_TroopValueState::instance.attackInfo.playerTotalTroopValueOfTroopsNearKeep[playerID]; int enemyTroopValue = DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + int isNotNervous = 1; int margin; if (enemyTroopValue < 50) margin = -20; @@ -31,18 +32,15 @@ namespace AI { margin = 500; if (troopValueNearKeep > enemyTroopValue + margin) { - DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 0; - return; - } - - if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue == 0) { + isNotNervous = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue == 0) { if (DAT_GameState::instance.playerDataArray[playerID] .totalEnemyTroopValueByPlayerID[DAT_GameSynchronyState::instance.currentPlayerSlotID] > troopValueNearKeep) MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous1BikFromPlayer, this)(playerID); MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playRequestHelpBikFromPlayer, this)(playerID); } - DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = 1; + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = isNotNervous; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index ca016fe0e..8b9c623de 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35513,7 +35513,7 @@ SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented, barring one register usage dif SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effectively 100% -SHC_3BB0A8C1_0x004D3780 | 74.0% | Constant 1 kept in register +SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3850 | 82.3% | AIC index folding differs From 939df02e12ed55bd9c4ea342d2764e5869974891 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:34 +0200 Subject: [PATCH 046/104] reimplement: SHC_3BB0A8C1_0x004D0280 100.00% Reimplemented --- src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp index a8c02b8b2..4ce6d8fee 100644 --- a/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp +++ b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp @@ -21,7 +21,7 @@ namespace AI { if (DAT_BuildingsState::instance.buildings[buildingID].owner != playerID) continue; - bool matches = false; + bool matches = true; if (buildingCategory == AINDBC_FEAR_FACTOR_BUILDINGS) matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CESSPIT || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BURNINGSTAKE diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 8b9c623de..e419109d7 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35399,7 +35399,7 @@ SHC_3BB0A8C1_0x004D0110 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0190 | 100.0% | Reimplemented, compiler didn't optimize one tail -SHC_3BB0A8C1_0x004D0280 | 98.2% | Reimplemented +SHC_3BB0A8C1_0x004D0280 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented From 2e43819f6e69affa831ee9caa46dc15085c405c4 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:35 +0200 Subject: [PATCH 047/104] reimplement: SHC_3BB0A8C1_0x004D38C0 100.00% Reimplemented --- .../AI/AICState/determineAIPlayerAttackRequestResponse.cpp | 5 ++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp index 6f54dca38..2ec92a0c9 100644 --- a/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp +++ b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp @@ -10,10 +10,9 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D38C0 BOOLEnum AICState::determineAIPlayerAttackRequestResponse(int playerID, undefined4 param_2, int requestedByPlayerID) { - int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; - if (aiType == OpenSHC::AI::AIT_NULL) + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) return FALSE; - int aicIndex = aiType - 1; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack1BikFromPlayerToPlayer, this)( diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index e419109d7..6ad1654ca 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35517,7 +35517,7 @@ SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3850 | 82.3% | AIC index folding differs -SHC_3BB0A8C1_0x004D38C0 | 83.8% | AIC index folding differs +SHC_3BB0A8C1_0x004D38C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented From a405be0afd5c7a9bb47af38732647faa702e80e1 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:35 +0200 Subject: [PATCH 048/104] reimplement: SHC_3BB0A8C1_0x004D3850 100.00% Reimplemented --- src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp | 5 ++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp index df8442f09..46df0cb27 100644 --- a/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp +++ b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp @@ -10,10 +10,9 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D3850 BOOLEnum AICState::determineAIPlayerHelp(int playerID, int requestedByPlayerID) { - int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; - if (aiType == OpenSHC::AI::AIT_NULL) + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) return FALSE; - int aicIndex = aiType - 1; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp1BikFromPlayerToPlayer, this)( diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6ad1654ca..47531bdf6 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35515,7 +35515,7 @@ SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effe SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3850 | 82.3% | AIC index folding differs +SHC_3BB0A8C1_0x004D3850 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D38C0 | 100.0% | Reimplemented From e30d4f40b223d59066845063ffb38950fc1b7742 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:35 +0200 Subject: [PATCH 049/104] reimplement: SHC_3BB0A8C1_0x004CE930 100.00% Reimplemented --- .../findAttackTribePositionAndPathToDefenses.cpp | 10 +++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp index db2abfa65..803bd3d6c 100644 --- a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp +++ b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp @@ -19,14 +19,14 @@ namespace AI { int y = 0; for (int i = 0; i < 11; i++) { int targetUnitID = 0; - for (int j = 0; j < DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; j++) { - int tribeID = DAT_GameState::instance.playerDataArray[playerID] - .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType + j]; + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; if (tribeID == 0) continue; if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID] - .aiTribeUIDs[j + DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType]) + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) continue; targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 47531bdf6..fed2945e3 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35333,7 +35333,7 @@ SHC_3BB0A8C1_0x004CE860 | 0.0% | Pending SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE930 | 91.2% | regalloc differs +SHC_3BB0A8C1_0x004CE930 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CEA50 | 92.7% | Reimplemented functionally, stack ordering problem From b0266c1b1017c11d623bab1aa9b37bb80fe953be Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:36 +0200 Subject: [PATCH 050/104] reimplement: SHC_3BB0A8C1_0x004CDBC0 100.00% Reimplemented --- .../getTargetableBuildingForPlayerID.cpp | 25 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp index b997e2dee..83fa94d22 100644 --- a/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp +++ b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp @@ -10,27 +10,26 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CDBC0 int AICState::getTargetableBuildingForPlayerID(int playerID, int param_2) { - int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; - if (aiType == 0) + if (DAT_GameState::instance.playerDataArray[playerID].aiType == 0) return 0; - int outerPatrolGroupsCount = this->aics[aiType - 1].OuterPatrolGroupsCount; - if (outerPatrolGroupsCount <= 0) + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (this->aics[aicIndex].OuterPatrolGroupsCount <= 0) return 0; - int tracker = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; - if (tracker <= 0) + if (DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker <= 0) return 0; + int step = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker + / this->aics[aicIndex].OuterPatrolGroupsCount; DAT_GameState::instance.playerDataArray[playerID].someCounter2++; - if (DAT_GameState::instance.playerDataArray[playerID].someCounter2 >= tracker) + if (DAT_GameState::instance.playerDataArray[playerID].someCounter2 + >= DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker) DAT_GameState::instance.playerDataArray[playerID].someCounter2 = 0; - - int index = (tracker / outerPatrolGroupsCount * param_2 - + DAT_GameState::instance.playerDataArray[playerID].someCounter2) - % tracker; - for (int i = 0; i < tracker; i++) { + int index = (step * param_2 + DAT_GameState::instance.playerDataArray[playerID].someCounter2) + % DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; index++; - if (index >= tracker) + if (index >= DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker) index = 0; if (buildingID == 0) continue; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index fed2945e3..59e320909 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35289,7 +35289,7 @@ SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDB20 | 34.3% | regalloc/spill differs -SHC_3BB0A8C1_0x004CDBC0 | 65.0% | Loop counter spilled in orig +SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented From 65994b3075f53d1d65d60db540d3fa79b94a746e Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:36 +0200 Subject: [PATCH 051/104] reimplement: SHC_3BB0A8C1_0x004CEA50 100.00% Reimplemented --- .../AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp | 6 +++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp index b9d4faf2f..d4f23d164 100644 --- a/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp +++ b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp @@ -31,11 +31,11 @@ namespace AI { != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) continue; + int x = DAT_GameState::instance.playerDataArray[playerID].someX; DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + int y = DAT_GameState::instance.playerDataArray[playerID].someY; uint result = MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, - DAT_TribesState::ptr)(tribeID, DAT_GameState::instance.playerDataArray[playerID].someX, - DAT_GameState::instance.playerDataArray[playerID].someY, 0, 0, - OpenSHC::Map::Units::Instructions::UMSE_0); + DAT_TribesState::ptr)(tribeID, x, y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); if (result == 0) MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::removeAllTribeUnits, DAT_TribesState::ptr)( tribeID); diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 59e320909..bc34aab76 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35335,7 +35335,7 @@ SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending SHC_3BB0A8C1_0x004CE930 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEA50 | 92.7% | Reimplemented functionally, stack ordering problem +SHC_3BB0A8C1_0x004CEA50 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CEB50 | 98.8% | Reimplemented From 3764d7c9df6667ecd30d7cebc470493775d6a87b Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:36 +0200 Subject: [PATCH 052/104] reimplement: SHC_3BB0A8C1_0x004CE430 100.00% Reimplemented --- .../AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp index b81a2726e..8d50e0976 100644 --- a/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp +++ b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp @@ -35,8 +35,8 @@ namespace AI { } // Hardcoded exception: the Sultan keeps a smaller distance than all other AIs - int extraDistance - = DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_SULTAN ? 8 : 20; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + int extraDistance = aicIndex == OpenSHC::AI::AIT_SULTAN - 1 ? 8 : 20; MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 10000, FALSE); From 7e9040d8fa7c860c8297843461aa78926e046908 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:37 +0200 Subject: [PATCH 053/104] reimplement: SHC_3BB0A8C1_0x004CF4F0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp index 8dd7eaed8..ccea76429 100644 --- a/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp +++ b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp @@ -13,17 +13,18 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF4F0 void AICState::sendAITribe_0xf_toAttackPatrol(int playerID) { + int tribeIndex = 15; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { return; } // Bug: only tribe 15 is sent, although tribe 16 can also exist if 'AIC: AttUnitPatrolGroupsCount' is 2 - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[15]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) { return; } if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[15]) { + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { return; } if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index bc34aab76..f17c2b75e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35361,7 +35361,7 @@ SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler impr SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF4F0 | 57.10% | Reimplemented functionally, our compiler improves on original by avoiding recomputations +SHC_3BB0A8C1_0x004CF4F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF560 | 37.74% | Reimplemented functionally, different register usage From 148c99a0457cd83069228ca4ae3fdf5cfe7588d3 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:37 +0200 Subject: [PATCH 054/104] reimplement: SHC_3BB0A8C1_0x004CF750 100.00% Reimplemented --- src/OpenSHC/AI/AICState/sendTribeToAttack.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp index 82f959aed..7ceb0cfef 100644 --- a/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp +++ b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp @@ -13,16 +13,17 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF750 void AICState::sendTribeToAttack(int playerID) { + int tribeIndex = 0xbd; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { return; } - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xbd]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) { return; } if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xbd]) { + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { return; } if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f17c2b75e..396827568 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35371,7 +35371,7 @@ SHC_3BB0A8C1_0x004CF660 | 97.2% | Reimplemented SHC_3BB0A8C1_0x004CF6E0 | 62.22% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CF750 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations +SHC_3BB0A8C1_0x004CF750 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF7C0 | 98.2% | Reimplemented From cf84238c4017fdef555deccad15199ade6d14cae Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:37 +0200 Subject: [PATCH 055/104] reimplement: SHC_3BB0A8C1_0x004D30E0 100.00% Reimplemented --- .../AI/AICState/sendUnitsToAttackBreachedCastle.cpp | 9 +++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp index 6346c66e0..026d0d65f 100644 --- a/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp +++ b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp @@ -90,10 +90,11 @@ namespace AI { vector += 1; } else { // Send units to attack lord - if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, - DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[defendingLordID].x, - DAT_UnitsState::instance.units[defendingLordID].y, 0, 0, - OpenSHC::Map::Units::Instructions::UMSE_0) + int lordY = DAT_UnitsState::instance.units[defendingLordID].y; + int lordX = DAT_UnitsState::instance.units[defendingLordID].x; + if (MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, lordX, lordY, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0) == 0) { return 0; } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 396827568..6296ddd17 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35509,7 +35509,7 @@ SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented, barring one register usage difference +SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effectively 100% From 3fc4aa3cbf632d499c017e122dd36b6f5f38a3ef Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:38 +0200 Subject: [PATCH 056/104] reimplement: SHC_3BB0A8C1_0x004CDE60 100.00% Reimplemented --- src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp | 5 ++--- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp index 90217ac8e..879635162 100644 --- a/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp +++ b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp @@ -11,10 +11,9 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CDE60 void AICState::setCurrentAttackStrength(int playerID) { - int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; - if (aiType == AITA_NULL) + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) return; - int aicIndex = aiType - 1; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; int randomPercent = SEC_RNG::instance.currentNumber2 % 100; MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6296ddd17..397873e3f 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35295,7 +35295,7 @@ SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDCF0 | 41.8% | Register allocation differs -SHC_3BB0A8C1_0x004CDE60 | 76.1% | AIC index folding differs +SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDF10 | 98.2% | Reimplemented From 3ad4530167ec961f60227d10e525c83d3628c28d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:38 +0200 Subject: [PATCH 057/104] reimplement: SHC_3BB0A8C1_0x004CF560 100.00% Reimplemented --- .../setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp index bedc117df..ceaf2f197 100644 --- a/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp +++ b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp @@ -14,6 +14,7 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF560 void AICState::setTribe0x11ToAggressiveAndAttackIfConditionMet(int playerID) { + int tribeIndex = 17; if (DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].moat2 <= 0) { return; } @@ -21,12 +22,12 @@ namespace AI { return; } - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[17]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) { return; } if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[17]) { + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { return; } if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 397873e3f..1e5a90fe9 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35363,7 +35363,7 @@ SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF4F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF560 | 37.74% | Reimplemented functionally, different register usage +SHC_3BB0A8C1_0x004CF560 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented From 468e5187792eb33ed828ba4586fd4768e437a85d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:38 +0200 Subject: [PATCH 058/104] reimplement: SHC_3BB0A8C1_0x004CF400 100.00% Reimplemented --- src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp index c60bbedf3..5f96df880 100644 --- a/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp +++ b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp @@ -13,16 +13,17 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF400 void AICState::setTribe0xCtoAggressiveAndAttack(int playerID) { + int tribeIndex = 12; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { return; } - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[12]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) { return; } if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[12]) { + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { return; } if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 1e5a90fe9..6cac03fea 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35357,7 +35357,7 @@ SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF2E0 | 83.4% | esi/edi regalloc differs -SHC_3BB0A8C1_0x004CF400 | 62.22% | Reimplemented functionally, our compiler improves on original by avoiding recomputations +SHC_3BB0A8C1_0x004CF400 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented From 922fa0cb3cd4ce734a58bc7ee5c10b5830f2012c Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:39 +0200 Subject: [PATCH 059/104] reimplement: SHC_3BB0A8C1_0x004CF6E0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp | 5 +++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp index 1dc2cf667..ec4cde70e 100644 --- a/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp +++ b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp @@ -13,16 +13,17 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CF6E0 void AICState::setTribe0xbToAggressiveAndAttack(int playerID) { + int tribeIndex = 11; if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { return; } - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[11]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; if (tribeID == 0) { return; } if (DAT_TribesState::instance.tribes[tribeID].uid - != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[11]) { + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { return; } if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 6cac03fea..dccd29d52 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35369,7 +35369,7 @@ SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF660 | 97.2% | Reimplemented -SHC_3BB0A8C1_0x004CF6E0 | 62.22% | Reimplemented functionally, different register usage +SHC_3BB0A8C1_0x004CF6E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF750 | 100.0% | Reimplemented From 8b730822cf5a01c307cb8682edd9d55f44c9ea60 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 20:43:48 +0200 Subject: [PATCH 060/104] tools/batch: quick_diff.py, and the source patterns it found in AI::AICState quick_diff.py compiles one file with /FA and diffs it against the original function disassembled from _original/, in about six seconds with no link and no reccmp run. It also shows the whole original function; reccmp cuts the original stream at the length of ours. Cheat sheet: field-copy locals let MSVC fold what the original kept separate, a constant array index held in a local stops the per-struct multiply being shared, result variables, add reg,-N as merged constants, argument locals around a store, break instead of return in loops, redundant loop guards. Co-Authored-By: Claude Fable 5.1 --- IMPLEMENTATION_CHEAT_SHEET.md | 9 ++ tools/reimplementation-helper/batch/README.md | 86 +++++++++---------- .../batch/quick_diff.py | 35 ++++++++ 3 files changed, 87 insertions(+), 43 deletions(-) diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index 074d8be07..92633bede 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -420,6 +420,15 @@ bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it So the predecessor-count rule tells you a duplicate _may_ be needed, not that it will help; `try_styles.py` is the only way to settle it. +- A loop whose early exit returns the same value as the code after the loop was a `break`, not a `return`. The tell is + callee-saved registers pushed _after_ the loop's entry test and one shared `mov eax, result` behind the loop, where + an early `return result;` makes us push everything in the prologue (`assignRequiredIdleEngineersToNewTribe`, + `addEngineersToSelection`, both ~88% -> 100%). +- Do not add a guard the loop condition already expresses. `if (id == 0) return 0;` in front of + `for (; id != 0; id = next(id))` moved one `push` across a branch; without it the compiler derives the same early + exit itself and places it where the original does (`aiRequiresExtraOxtethers`). Likewise put the block the + original keeps inline first: `if (best > 20) { store; return 1; } return 0;` rather than the inverted guard. + ### GOTO A function may contain multiple GOTOs. diff --git a/tools/reimplementation-helper/batch/README.md b/tools/reimplementation-helper/batch/README.md index 6cfda14e2..08b5d17c6 100644 --- a/tools/reimplementation-helper/batch/README.md +++ b/tools/reimplementation-helper/batch/README.md @@ -10,56 +10,56 @@ output goes to `build-RelWithDebInfo/batch/` (git-ignored). A recent `clang-form (`pip install clang-format`, or set `CLANG_FORMAT`) is needed for the scripts that rewrite sources, because the Visual Studio bundled one is too old for `.clang-format`. -| Script | Purpose | -|---|---| -| `build_quiet.py [--keep-going]` | `build.bat RelWithDebInfo OpenSHC.dll`, prints only errors and `BUILD_OK`/`BUILD_FAIL` (build.bat exits 0 even on errors). Retries once on a PDB server error, see the note below. | -| `syntax_check.py [substr...]` | `cl /Zs` every (matching) list file serially; finds all compile errors in one pass without linking. | -| `reccmp_report.py [--run] pct\|save\|cmp\|diff` | Match % and *normalized* % (call/tail-jump targets and resolver addresses ignored) per function, snapshots, before/after comparison, compact asm diffs. | -| `orig_asm.py NAME [--both\|--stats]` | The **original** instruction stream of one function (not the interleaved diff), in address order; `--stats` reports the tells that decide how to reimplement it: `mov reg, 0` (handwritten asm), frame pointer (built /Od), frame size, jump tables. | -| `show_functions.py PREFIX [--skip-100]` | Print function bodies under `src/OpenSHC/PREFIX` with their percentages, in the block format `splice_functions.py` reads. | -| `splice_functions.py BLOCKS.txt` | Replace many function bodies at once (keeps the `// FUNCTION:` line, can add includes/usings), then clang-format. | -| `commit_progress_batch.py PREFIX... [Name=remark]` | One `reimplement:` commit per changed function with its status line, like `commit_progress`. | -| `fix_func_refs.py [--drop-include H]` | After a header refactor: rewrite `X_Func::name` references whose resolver moved to another namespace. | -| `move_to_resolver_namespace.py FILE...` | After a header refactor: re-wrap a definition in its resolver's new namespace and `git mv` it to the matching folder. | - -Analysis helpers, for deciding *what* to change when a function is stuck below 100%: - -| Script | Purpose | -|---|---| -| `diff_triage.py [--run] [filter]` | One line per function: match %, raw and register/slot-normalized difflib ratios, `sub esp` frame size per side, byte-sized `[esp]` accesses per side, `ret` placement. Flags `FRAME` / `BYTE` / `RET` / `alloc-only`. | -| `diff_slots.py NAME` | Every `[esp + N]` slot with its access count on each side, and the slots only we use. Run after `diff_triage.py` reports `FRAME`. | -| `diff_jcc.py [NAME]` | Comparisons whose operator is one strictness step off the original (`jl`/`jle`, `jg`/`jge`, `ja`/`jae`) or whose `cmp` operands are the other way round, with context. | -| `diff_types.py [--run] [NAME]` | The non-comparison counterpart of `diff_jcc.py`: `movsx` against `movzx` for the same field (its signedness is wrong in the generated header), `add` against `sub` with the same magnitude (a sign error in a formula - how `2400/n + 40` was caught being `- 40`). It pairs only equal-length runs inside one reccmp block, so it stays quiet once the streams drift: it reports nothing across the 101 functions of `Game::GameStateStructures` and fires on both bugs when they are reintroduced. | -| `quick_diff.py FILE.cpp... [-q]` | Compile one file with `/FA` and diff it against the original function disassembled straight from `_original/` - about 6 seconds, no link and no reccmp, so a dozen source variants can be tried in the time of one build. Shows the whole original function, which reccmp's diff does not when ours is shorter. A triage loop: confirm a 1.0 with `reccmp_report.py --run`, and do not trust a *rise* of its ratio below about 0.6 - on a badly matching function difflib rewards a different alignment, and one such "gain" was a 10-point loss in reccmp. | -| `push_pattern.py FILE.cpp...` | Per call, the argument-push run as `pushes/interleaved` for both sides. A neat run (`0` interleaved) means every argument was already in a register or slot, i.e. the source had a **local**; a mixed run is what writing the expression inline at the call produces. A starred row is a call where the two sides disagree - add the local, or drop it, accordingly. Counts only calls whose set-up it can see, so a differing call *total* means our source has an extra call (a duplicated block), not that the tool failed. | -| `reorder_search.py FILE NAME [--pairs] [--dry]` | Hill-climb the match % by reordering independent statements: permute runs of >=3 by default, or swap every adjacent independent pair with `--pairs`. Builds once per move and reverts anything that does not improve. | +| Script | Purpose | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `build_quiet.py [--keep-going]` | `build.bat RelWithDebInfo OpenSHC.dll`, prints only errors and `BUILD_OK`/`BUILD_FAIL` (build.bat exits 0 even on errors). Retries once on a PDB server error, see the note below. | +| `syntax_check.py [substr...]` | `cl /Zs` every (matching) list file serially; finds all compile errors in one pass without linking. | +| `reccmp_report.py [--run] pct\|save\|cmp\|diff` | Match % and _normalized_ % (call/tail-jump targets and resolver addresses ignored) per function, snapshots, before/after comparison, compact asm diffs. | +| `orig_asm.py NAME [--both\|--stats]` | The **original** instruction stream of one function (not the interleaved diff), in address order; `--stats` reports the tells that decide how to reimplement it: `mov reg, 0` (handwritten asm), frame pointer (built /Od), frame size, jump tables. | +| `show_functions.py PREFIX [--skip-100]` | Print function bodies under `src/OpenSHC/PREFIX` with their percentages, in the block format `splice_functions.py` reads. | +| `splice_functions.py BLOCKS.txt` | Replace many function bodies at once (keeps the `// FUNCTION:` line, can add includes/usings), then clang-format. | +| `commit_progress_batch.py PREFIX... [Name=remark]` | One `reimplement:` commit per changed function with its status line, like `commit_progress`. | +| `fix_func_refs.py [--drop-include H]` | After a header refactor: rewrite `X_Func::name` references whose resolver moved to another namespace. | +| `move_to_resolver_namespace.py FILE...` | After a header refactor: re-wrap a definition in its resolver's new namespace and `git mv` it to the matching folder. | + +Analysis helpers, for deciding _what_ to change when a function is stuck below 100%: + +| Script | Purpose | +| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `diff_triage.py [--run] [filter]` | One line per function: match %, raw and register/slot-normalized difflib ratios, `sub esp` frame size per side, byte-sized `[esp]` accesses per side, `ret` placement. Flags `FRAME` / `BYTE` / `RET` / `alloc-only`. | +| `diff_slots.py NAME` | Every `[esp + N]` slot with its access count on each side, and the slots only we use. Run after `diff_triage.py` reports `FRAME`. | +| `diff_jcc.py [NAME]` | Comparisons whose operator is one strictness step off the original (`jl`/`jle`, `jg`/`jge`, `ja`/`jae`) or whose `cmp` operands are the other way round, with context. | +| `diff_types.py [--run] [NAME]` | The non-comparison counterpart of `diff_jcc.py`: `movsx` against `movzx` for the same field (its signedness is wrong in the generated header), `add` against `sub` with the same magnitude (a sign error in a formula - how `2400/n + 40` was caught being `- 40`). It pairs only equal-length runs inside one reccmp block, so it stays quiet once the streams drift: it reports nothing across the 101 functions of `Game::GameStateStructures` and fires on both bugs when they are reintroduced. | +| `quick_diff.py FILE.cpp... [-q]` | Compile one file with `/FA` and diff it against the original function disassembled straight from `_original/` - about 6 seconds, no link and no reccmp, so a dozen source variants can be tried in the time of one build. Shows the whole original function, which reccmp's diff does not when ours is shorter. A triage loop: confirm a 1.0 with `reccmp_report.py --run`, and do not trust a _rise_ of its ratio below about 0.6 - on a badly matching function difflib rewards a different alignment, and one such "gain" was a 10-point loss in reccmp. | +| `push_pattern.py FILE.cpp...` | Per call, the argument-push run as `pushes/interleaved` for both sides. A neat run (`0` interleaved) means every argument was already in a register or slot, i.e. the source had a **local**; a mixed run is what writing the expression inline at the call produces. A starred row is a call where the two sides disagree - add the local, or drop it, accordingly. Counts only calls whose set-up it can see, so a differing call _total_ means our source has an extra call (a duplicated block), not that the tool failed. | +| `reorder_search.py FILE NAME [--pairs] [--dry]` | Hill-climb the match % by reordering independent statements: permute runs of >=3 by default, or swap every adjacent independent pair with `--pairs`. Builds once per move and reverts anything that does not improve. | ### Choosing what to work on -| Script | Purpose | -|---|---| -| `rank_functions.py [filter] [--top N]` | Rank by source lines x (1 - match), so the big badly-matching functions come first instead of the small ones with a low percentage. | -| `scan_dispatch.py [filter]` | Functions whose dispatch form disagrees with the original (jump table on one side, if/else-if chain on the other), and which way to fix each. | -| `scan_chains.py [filter] [--min N]` | `if/else if` chains comparing one expression against several constants: the candidates to convert when `scan_dispatch.py` says the original has a table we do not. | -| `try_styles.py FUNC [style...]` | Compile and measure several hand-written variants of one function, restore the winner, print the table. Variants live in `build-RelWithDebInfo/batch/variants/FUNC/*.cpp`. | +| Script | Purpose | +| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `rank_functions.py [filter] [--top N]` | Rank by source lines x (1 - match), so the big badly-matching functions come first instead of the small ones with a low percentage. | +| `scan_dispatch.py [filter]` | Functions whose dispatch form disagrees with the original (jump table on one side, if/else-if chain on the other), and which way to fix each. | +| `scan_chains.py [filter] [--min N]` | `if/else if` chains comparing one expression against several constants: the candidates to convert when `scan_dispatch.py` says the original has a table we do not. | +| `try_styles.py FUNC [style...]` | Compile and measure several hand-written variants of one function, restore the winner, print the table. Variants live in `build-RelWithDebInfo/batch/variants/FUNC/*.cpp`. | ### Readability passes These undo decompiler artefacts. Each measured as code-generation neutral or better where it was applied, but none is guaranteed to be -- re-measure after running one. -| Script | Purpose | -|---|---| -| `declare_at_use.py [filter]` | Move the leading declaration block down to each variable's first use, and drop declarations that are never used. Only moves one when the first assignment provably dominates every later use. | -| `decast.py [filter]` | Collapse doubled casts, `(int)((int)(x))` -> `(int)(x)`. Skips `(int)((int)X + Y)`, where the inner cast covers one operand and dropping it would move the conversion onto the sum. | -| `deparen.py [filter] [--apply]` | Drop redundant brackets from `if`/`while` conditions using C precedence. Keeps the load-bearing ones (`(a \|\| b) && c`, `(x & 7) == 0`, assignments and comma operators inside conditions). Dry run unless `--apply`. | -| `test_deparen.py` | Precedence tests for `deparen.py`. Run after touching its table: an earlier version silently rewrote `(a != 0 \|\| b != 0) && (x & 0x100) == 0` into a different expression that still compiled. | -| `undiv.py [filter] [--apply]` | Turn MSVC's biased shift for a signed power-of-two division back into a division: `(x + (x >> 0x1f & 7U)) >> 3` -> `x / 8`. Dry run unless `--apply`. Usually a large gain rather than merely neutral: over one namespace it was worth up to 22% on a single function. | -| `test_undiv.py` | Cases for `undiv.py`, including the near-misses it must leave alone: a shift disagreeing with the mask, a mask that is not 2^k - 1, and a bias taken from another variable. | -| `unmod.py [filter] [--apply]` | The same for a signed power-of-two remainder: `v = x & 0x8000000f;` plus the negative-case repair is `v = x % 16`. Forces a signed dividend, because the repair only exists for a signed operand and an unsigned `%` compiles to a bare `and`. | -| `test_unmod.py` | Cases for `unmod.py`: a repair constant that does not complement the mask, a repair applied to another variable, and a bare mask with no repair. | -| `unptr.py [filter] [--apply]` | Replace the decompiler's field-walking pointers (`piVar1 = &x.f; *piVar1 = *piVar1 + 1;`) with the field itself. Only the two-adjacent-line shape, since a pointer freezes the address while the field form re-evaluates the index. Reports which pointers are still in use. | -| `test_unptr.py` | Cases for `unptr.py`, including a pointer declared on the spot (`char* pcVar3 = &...`, whose type prefix must go with it) and the set-up-then-use-later shape it must leave alone. | +| Script | Purpose | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `declare_at_use.py [filter]` | Move the leading declaration block down to each variable's first use, and drop declarations that are never used. Only moves one when the first assignment provably dominates every later use. | +| `decast.py [filter]` | Collapse doubled casts, `(int)((int)(x))` -> `(int)(x)`. Skips `(int)((int)X + Y)`, where the inner cast covers one operand and dropping it would move the conversion onto the sum. | +| `deparen.py [filter] [--apply]` | Drop redundant brackets from `if`/`while` conditions using C precedence. Keeps the load-bearing ones (`(a \|\| b) && c`, `(x & 7) == 0`, assignments and comma operators inside conditions). Dry run unless `--apply`. | +| `test_deparen.py` | Precedence tests for `deparen.py`. Run after touching its table: an earlier version silently rewrote `(a != 0 \|\| b != 0) && (x & 0x100) == 0` into a different expression that still compiled. | +| `undiv.py [filter] [--apply]` | Turn MSVC's biased shift for a signed power-of-two division back into a division: `(x + (x >> 0x1f & 7U)) >> 3` -> `x / 8`. Dry run unless `--apply`. Usually a large gain rather than merely neutral: over one namespace it was worth up to 22% on a single function. | +| `test_undiv.py` | Cases for `undiv.py`, including the near-misses it must leave alone: a shift disagreeing with the mask, a mask that is not 2^k - 1, and a bias taken from another variable. | +| `unmod.py [filter] [--apply]` | The same for a signed power-of-two remainder: `v = x & 0x8000000f;` plus the negative-case repair is `v = x % 16`. Forces a signed dividend, because the repair only exists for a signed operand and an unsigned `%` compiles to a bare `and`. | +| `test_unmod.py` | Cases for `unmod.py`: a repair constant that does not complement the mask, a repair applied to another variable, and a bare mask with no repair. | +| `unptr.py [filter] [--apply]` | Replace the decompiler's field-walking pointers (`piVar1 = &x.f; *piVar1 = *piVar1 + 1;`) with the field itself. Only the two-adjacent-line shape, since a pointer freezes the address while the field form re-evaluates the index. Reports which pointers are still in use. | +| `test_unptr.py` | Cases for `unptr.py`, including a pointer declared on the spot (`char* pcVar3 = &...`, whose type prefix must go with it) and the set-up-then-use-later shape it must leave alone. | ## Typical loop @@ -91,7 +91,7 @@ reading `reccmp_report.py diff NAME` and judging by eye whether the two streams The two ratios are the quickest way to tell whether more source work can pay: -- `norm` much higher than `raw` - only register and stack-slot *naming* differs. +- `norm` much higher than `raw` - only register and stack-slot _naming_ differs. The allocator is not reachable from the source; record the reason and move on. - `norm` still low - real structure or ordering differs; keep looking. - `FRAME` - one local too many or too few. Chase it with `diff_slots.py`; this has @@ -123,7 +123,7 @@ bugs that Ghidra had decompiled wrongly. - `build_quiet.py` and `syntax_check.py` both retry such a failure twice, because it has two causes. A wedged server survives the kill `build.bat` does up front, so the first retry stops this worktree's `mspdbsrv.exe` (`common.kill_pdb_server`, never other worktrees'). A wedged - *endpoint* outlives every `mspdbsrv.exe` using it, so the second retry moves to a fresh name + _endpoint_ outlives every `mspdbsrv.exe` using it, so the second retry moves to a fresh name (`common.fresh_pdb_endpoint`) rather than retrying into one that will keep failing. - In `syntax_check.py` a PDB failure is not just a lost run: the fatal line counts as an error against whichever file hit it and the rest of the batch still gets checked, so without the diff --git a/tools/reimplementation-helper/batch/quick_diff.py b/tools/reimplementation-helper/batch/quick_diff.py index ab36a2bdd..13c759456 100644 --- a/tools/reimplementation-helper/batch/quick_diff.py +++ b/tools/reimplementation-helper/batch/quick_diff.py @@ -27,9 +27,14 @@ import common quiet = '-q' in sys.argv +<<<<<<< HEAD keep_slots = '-s' in sys.argv files = [os.path.abspath(f) for f in sys.argv[1:] if f not in ('-q', '-s')] if not files and __name__ == "__main__": +======= +files = [os.path.abspath(f) for f in sys.argv[1:] if f != '-q'] +if not files: +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) sys.exit(__doc__) ROOT = str(common.ROOT).replace(chr(92), '/') os.chdir(ROOT) @@ -111,6 +116,7 @@ def ours(f, cls, name): print('no code found for %s::%s' % (cls, name)) return None res = [] +<<<<<<< HEAD # `_x$ = -16` lines define each local's offset; `_x$[esp+44H]` then means [esp + 0x44 - 16] pre = txt[txt.rfind('_TEXT SEGMENT', 0, m.start()):m.start()] slots = {k: int(v) for k, v in re.findall(r'^(\w+\$\w*|tv\d+) = (-?\d+)', pre, re.M)} @@ -129,6 +135,13 @@ def slot(mm): continue l = l.replace('\t', ' ') l = re.sub(r'(\w+\$\w*|tv\d+)\[esp(?:([+-][0-9a-fA-F]+H|[+-]\d+))?\]', slot, l) +======= + for l in m.group(2).splitlines(): + l = l.split(';')[0].strip() + if not l or l.endswith(':') or re.match(r'^\w+\$ = ', l) or l.startswith('npad') or l.startswith('$') or l.startswith('DD') or l.startswith('DB') or l.startswith('_'): + continue + l = l.replace('\t', ' ') +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) op = l.split(' ')[0] if op.startswith('j') and 'DWORD PTR' not in l: l = op + (' L' if '$' in l else ' F') @@ -157,8 +170,12 @@ def slot(mm): def canon(s): # esp-relative slots and globals compare loosely +<<<<<<< HEAD if not keep_slots: s = re.sub(r'\[esp( [+-] 0x[0-9a-f]+)?\]', '[esp+N]', s) +======= + s = re.sub(r'\[esp( [+-] 0x[0-9a-f]+)?\]', '[esp+N]', s) +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) s = re.sub(r'tv\d+\[', '[', s) s = re.sub(r'_\w+\$\w*\[', '[', s) s = s.replace('mov eax, G', 'mov eax, 0x51eb851f') if s == 'mov eax, G' else s @@ -166,6 +183,7 @@ def canon(s): return s +<<<<<<< HEAD def streams(f): """(original, ours) normalized instruction lists for one source file.""" src = open(f, encoding='utf-8').read() @@ -190,3 +208,20 @@ def main(): if __name__ == '__main__': main() +======= +for f in files: + src = open(f, encoding='utf-8').read() + va = int(re.search(r'FUNCTION: STRONGHOLDCRUSADER (0x[0-9A-Fa-f]+)', src).group(1), 16) + a = [canon(x) for x in orig(va)] + cls, name = re.search(r'FUNCTION: STRONGHOLDCRUSADER[^\n]*\n[^\n(]*?(\w+)::(\w+)\(', src).groups() + b = ours(f, cls, name) + if b is None: + continue + b = [canon(x) for x in b] + ratio = difflib.SequenceMatcher(None, a, b, autojunk=False).ratio() + print('##### %s ratio %.4f orig %d ours %d' % (os.path.basename(f), ratio, len(a), len(b))) + if not quiet: + for l in difflib.unified_diff(a, b, lineterm='', n=2): + if not l.startswith(('---', '+++')): + print(l) +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) From a39914dfda73c989e4da5d65a75394dcc954c168 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:06 +0200 Subject: [PATCH 061/104] reimplement: SHC_3BB0A8C1_0x004C6D60 100.00% Reimplemented --- .../AI/AICState/setAICParameters_01.cpp | 18 +++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_01.cpp b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp index dc4631bf4..6da83f2a3 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_01.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp @@ -13,15 +13,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C6D60 void AICState::setAICParameters_01(int aicIndex) { - this->aics[aicIndex].minimumCheese = -1; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; - this->aics[aicIndex].aiRequestDelay = 0x12; - this->aics[aicIndex].populationPerQuarry = 0x12; - this->aics[aicIndex].populationPerFarm = 6; - this->aics[aicIndex].maxWoodcutters = 6; - this->aics[aicIndex].maxFarms = 6; this->aics[aicIndex].flagType = 0xc; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -34,11 +25,17 @@ namespace AI { this->aics[aicIndex].taxesMin = 3; this->aics[aicIndex].taxesMax = 10; this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].populationPerFarm = 6; this->aics[aicIndex].unknown011 = 0x14; this->aics[aicIndex].maxFood = 100; this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x12; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; this->aics[aicIndex].doubleRationsFoodThreshold = 0; this->aics[aicIndex].maxWood = 0x1e; @@ -49,11 +46,14 @@ namespace AI { this->aics[aicIndex].maxResourceVariance = 5; this->aics[aicIndex].recruitGoldThreshold = 200; this->aics[aicIndex].populationPerWoodcutter = 8; + this->aics[aicIndex].populationPerQuarry = 0x12; this->aics[aicIndex].populationPerIronmine = 0; this->aics[aicIndex].populationPerPitchrig = 0; this->aics[aicIndex].maxQuarries = 1; this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 6; this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 6; this->aics[aicIndex].buildInterval = 1; this->aics[aicIndex].resourceRebuildDelay = 10; this->aics[aicIndex].blacksmithSetting = 0x16; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index dccd29d52..c02ad8a02 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34157,7 +34157,7 @@ SHC_3BB0A8C1_0x004C6CD0 | 0.0% | Pending SHC_3BB0A8C1_0x004C6D30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C6D60 | 92.2% | Constant store order differs +SHC_3BB0A8C1_0x004C6D60 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7120 | 83.7% | Constant store order differs From b28955a397377d1d1035485640d0f5b36c6dc58d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:07 +0200 Subject: [PATCH 062/104] reimplement: SHC_3BB0A8C1_0x004C7120 100.00% Reimplemented --- .../AI/AICState/setAICParameters_02.cpp | 46 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_02.cpp b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp index 281539c43..339becf6f 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_02.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp @@ -13,20 +13,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C7120 void AICState::setAICParameters_02(int aicIndex) { - this->aics[aicIndex].minimumBread = 0xffffffff; - this->aics[aicIndex].minimumWheat = 0xffffffff; - this->aics[aicIndex].minimumHop = 0xffffffff; - this->aics[aicIndex].populationPerFarm = 5; - this->aics[aicIndex].unknown011 = 0xf; - this->aics[aicIndex].tradeAmountFood = 5; - this->aics[aicIndex].aiRequestDelay = 0xc; - this->aics[aicIndex].maxResourceOther = 0xf; - this->aics[aicIndex].maxEquipment = 0xc; - this->aics[aicIndex].maxBeer = 5; - this->aics[aicIndex].maxResourceVariance = 5; - this->aics[aicIndex].populationPerWoodcutter = 5; - this->aics[aicIndex].populationPerQuarry = 0xf; - this->aics[aicIndex].resourceRebuildDelay = 0xc; this->aics[aicIndex].flagType = 0xc; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -39,15 +25,28 @@ namespace AI { this->aics[aicIndex].taxesMin = 3; this->aics[aicIndex].taxesMax = 0xb; this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xf; this->aics[aicIndex].maxFood = 0x96; this->aics[aicIndex].minimumApples = 10; this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 0xffffffff; + this->aics[aicIndex].tradeAmountFood = 5; this->aics[aicIndex].tradeAmountEquipment = 6; + this->aics[aicIndex].aiRequestDelay = 0xc; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; this->aics[aicIndex].maxWood = 0x30; this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0xc; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 5; + this->aics[aicIndex].populationPerQuarry = 0xf; this->aics[aicIndex].populationPerIronmine = 0; this->aics[aicIndex].populationPerPitchrig = 0; this->aics[aicIndex].maxQuarries = 2; @@ -56,6 +55,7 @@ namespace AI { this->aics[aicIndex].maxPitchrigs = 1; this->aics[aicIndex].maxFarms = 10; this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0xc; this->aics[aicIndex].blacksmithSetting = 0x16; this->aics[aicIndex].fletcherSetting = 0x11; this->aics[aicIndex].poleturnerSetting = 0x13; @@ -77,38 +77,38 @@ namespace AI { this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; this->aics[aicIndex].unknown072 = 400; this->aics[aicIndex].unknown073 = 0x3c; - this->aics[aicIndex].recruitProbDefStrong = 0x14; - this->aics[aicIndex].SortieUnitRanged = 0x16; - this->aics[aicIndex].DefDiggingUnitMax = 0x14; - this->aics[aicIndex].DefUnit1 = 0x16; - this->aics[aicIndex].RaidUnitsRandom = 10; this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x1e; this->aics[aicIndex].recruitProbRaidDefault = 0x28; this->aics[aicIndex].recruitProbRaidStrong = 0x28; this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; this->aics[aicIndex].recruitProbAttackStrong = 0x28; this->aics[aicIndex].RecruitIntervalWeak = 0; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 1; this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitRanged = 0x16; this->aics[aicIndex].SortieUnitMeleeMin = 8; this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnitMax = 0x14; this->aics[aicIndex].DefDiggingUnit = 0x18; this->aics[aicIndex].DefWalls = 0x32; this->aics[aicIndex].DefTotal = 0x50; this->aics[aicIndex].OuterPatrolGroupsCount = 3; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; this->aics[aicIndex].RaidUnit1 = 0x47; this->aics[aicIndex].RaidUnit2 = 0x47; this->aics[aicIndex].RaidUnit3 = 0x47; this->aics[aicIndex].RaidUnit4 = 0x48; this->aics[aicIndex].HarassingSiegeEnginesMax = 1; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; - this->aics[aicIndex].recruitProbDefDefault = 0x1e; - this->aics[aicIndex].recruitProbRaidWeak = 0x1e; - this->aics[aicIndex].recruitProbAttackDefault = 0x1e; - this->aics[aicIndex].RaidUnitsBase = 0x1e; this->aics[aicIndex].RaidRetargetDelay = 3; this->aics[aicIndex].AttForceBase = 0x1e; this->aics[aicIndex].AttForceRandom = 10; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c02ad8a02..9d17d66ac 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34159,7 +34159,7 @@ SHC_3BB0A8C1_0x004C6D30 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C6D60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7120 | 83.7% | Constant store order differs +SHC_3BB0A8C1_0x004C7120 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7520 | 94.1% | Constant store order differs From 691050e39598b8d162d46bc61992337de20934a3 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:07 +0200 Subject: [PATCH 063/104] reimplement: SHC_3BB0A8C1_0x004C7520 100.00% Reimplemented --- .../AI/AICState/setAICParameters_03.cpp | 26 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_03.cpp b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp index 83053911a..d4429b581 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_03.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp @@ -13,8 +13,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C7520 void AICState::setAICParameters_03(int aicIndex) { - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].flagType = 0xc; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -33,6 +31,8 @@ namespace AI { this->aics[aicIndex].minimumApples = 10; this->aics[aicIndex].minimumCheese = 10; this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 5; this->aics[aicIndex].tradeAmountEquipment = 4; this->aics[aicIndex].aiRequestDelay = 0xc; @@ -80,26 +80,30 @@ namespace AI { this->aics[aicIndex].recruitProbDefStrong = 0x1e; this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x46; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 4; this->aics[aicIndex].SortieUnitRangedMin = 4; - this->aics[aicIndex].SortieUnitMeleeMin = 5; this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMeleeMin = 5; this->aics[aicIndex].SortieUnitMelee = 0x1a; - this->aics[aicIndex].OuterPatrolRallyDelay = 0x14; - this->aics[aicIndex].RaidUnitsRandom = 5; - this->aics[aicIndex].recruitProbRaidStrong = 0; - this->aics[aicIndex].recruitProbAttackWeak = 0; - this->aics[aicIndex].recruitProbAttackDefault = 0x28; - this->aics[aicIndex].recruitProbAttackStrong = 0x46; this->aics[aicIndex].DefDiggingUnitMax = 0; this->aics[aicIndex].DefDiggingUnit = 0x18; this->aics[aicIndex].DefWalls = 0x1e; this->aics[aicIndex].DefTotal = 0x28; this->aics[aicIndex].OuterPatrolGroupsCount = 2; this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 0x14; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnit1 = 0x1a; this->aics[aicIndex].HarassingSiegeEnginesMax = 2; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 3; @@ -107,10 +111,6 @@ namespace AI { this->aics[aicIndex].AttForceRandom = 10; this->aics[aicIndex].AttForceSupportAllyThreshold = 10; this->aics[aicIndex].AttForceRallyPercentage = 0x3c; - this->aics[aicIndex].DefUnit1 = 0x17; - this->aics[aicIndex].DefUnit2 = 0x17; - this->aics[aicIndex].DefUnit3 = 0x17; - this->aics[aicIndex].RaidUnit1 = 0x1a; this->aics[aicIndex].AttMovingTribesThreshold = 100; this->aics[aicIndex].AttAssaultDelay = 5; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0xf; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 9d17d66ac..4f992621b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34161,7 +34161,7 @@ SHC_3BB0A8C1_0x004C6D60 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7520 | 94.1% | Constant store order differs +SHC_3BB0A8C1_0x004C7520 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7910 | 81.9% | Constant store order differs From 6ae05f6e2d7b6f5f4c1fc454aa0b721fbacafefd Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:08 +0200 Subject: [PATCH 064/104] reimplement: SHC_3BB0A8C1_0x004C7910 100.00% Reimplemented --- .../AI/AICState/setAICParameters_04.cpp | 26 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_04.cpp b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp index f2043fc77..2bcec1b88 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_04.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp @@ -13,29 +13,29 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C7910 void AICState::setAICParameters_04(int aicIndex) { - this->aics[aicIndex].unknown004 = 1; - this->aics[aicIndex].unknown005 = 1; - this->aics[aicIndex].farm2 = 0x20; - this->aics[aicIndex].farm4 = 0x20; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].flagType = 0xd; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; this->aics[aicIndex].criticalPopularity = 0x1194; this->aics[aicIndex].lowestPopularity = 6000; this->aics[aicIndex].highestPopularity = 9000; this->aics[aicIndex].taxesMin = 3; this->aics[aicIndex].taxesMax = 0xb; this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].farm4 = 0x20; this->aics[aicIndex].populationPerFarm = 5; this->aics[aicIndex].unknown011 = 0xc; this->aics[aicIndex].maxFood = 200; this->aics[aicIndex].minimumApples = 5; this->aics[aicIndex].minimumCheese = 5; this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 4; this->aics[aicIndex].aiRequestDelay = 0x14; @@ -78,16 +78,12 @@ namespace AI { this->aics[aicIndex].recruitProbRaidWeak = 10; this->aics[aicIndex].recruitProbRaidDefault = 0x1e; this->aics[aicIndex].recruitProbRaidStrong = 10; - this->aics[aicIndex].recruitProbAttackDefault = 10; - this->aics[aicIndex].RecruitInterval = 4; - this->aics[aicIndex].RecruitIntervalStrong = 4; - this->aics[aicIndex].OuterPatrolGroupsCount = 4; - this->aics[aicIndex].HarassingSiegeEnginesMax = 4; this->aics[aicIndex].recruitProbAttackWeak = 0; - this->aics[aicIndex].OuterPatrolRallyDelay = 3; - this->aics[aicIndex].RaidUnitsRandom = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 10; this->aics[aicIndex].recruitProbAttackStrong = 0x3c; this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; this->aics[aicIndex].SortieUnitRangedMin = 8; this->aics[aicIndex].SortieUnitRanged = 0x46; this->aics[aicIndex].SortieUnitMeleeMin = 6; @@ -96,14 +92,18 @@ namespace AI { this->aics[aicIndex].DefDiggingUnit = 0x19; this->aics[aicIndex].DefWalls = 0x50; this->aics[aicIndex].DefTotal = 100; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 3; this->aics[aicIndex].DefUnit1 = 0x17; this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].RaidUnitsRandom = 0x14; this->aics[aicIndex].DefUnit3 = 0x16; this->aics[aicIndex].DefUnit4 = 0x16; this->aics[aicIndex].RaidUnitsBase = 5; this->aics[aicIndex].RaidUnit1 = 0x4a; this->aics[aicIndex].RaidUnit2 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 5; this->aics[aicIndex].AttForceBase = 0x28; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 4f992621b..c46ee24dc 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34163,7 +34163,7 @@ SHC_3BB0A8C1_0x004C7120 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7910 | 81.9% | Constant store order differs +SHC_3BB0A8C1_0x004C7910 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7D20 | 81.5% | Constant store order differs From 15eba88da48772a1ad77e2acc69f73d6e67d710b Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:08 +0200 Subject: [PATCH 065/104] reimplement: SHC_3BB0A8C1_0x004C7D20 100.00% Reimplemented --- .../AI/AICState/setAICParameters_05.cpp | 58 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_05.cpp b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp index 52b7ad888..b87465a08 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_05.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp @@ -13,17 +13,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C7D20 void AICState::setAICParameters_05(int aicIndex) { - this->aics[aicIndex].maxFood = 0x32; - this->aics[aicIndex].maxStone = 0x32; - this->aics[aicIndex].taxesMin = 3; - this->aics[aicIndex].taxesMax = 0xc; - this->aics[aicIndex].farm2 = 0x1e; - this->aics[aicIndex].farm3 = 0x1e; - this->aics[aicIndex].populationPerFarm = 3; - this->aics[aicIndex].populationPerIronmine = 0xc; - this->aics[aicIndex].maxQuarries = 4; - this->aics[aicIndex].maxIronmines = 4; - this->aics[aicIndex].defWallPatrolGroups = 4; this->aics[aicIndex].flagType = 10; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -33,9 +22,15 @@ namespace AI { this->aics[aicIndex].criticalPopularity = 4000; this->aics[aicIndex].lowestPopularity = 5000; this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].populationPerFarm = 3; this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 0x32; this->aics[aicIndex].minimumApples = 5; this->aics[aicIndex].minimumCheese = 5; this->aics[aicIndex].minimumBread = 5; @@ -47,6 +42,7 @@ namespace AI { this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x14; this->aics[aicIndex].doubleRationsFoodThreshold = 0x14; this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; this->aics[aicIndex].maxResourceOther = 10; this->aics[aicIndex].maxEquipment = 5; this->aics[aicIndex].maxBeer = 10; @@ -54,7 +50,10 @@ namespace AI { this->aics[aicIndex].recruitGoldThreshold = 200; this->aics[aicIndex].populationPerWoodcutter = 6; this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 4; this->aics[aicIndex].maxWoodcutters = 0xe; this->aics[aicIndex].maxPitchrigs = 1; this->aics[aicIndex].maxFarms = 0xf; @@ -71,46 +70,47 @@ namespace AI { this->aics[aicIndex].sellResource11 = 0x14; this->aics[aicIndex].sellResource12 = 0x15; this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolGroups = 4; this->aics[aicIndex].unknown072 = 100; this->aics[aicIndex].unknown073 = 0x14; this->aics[aicIndex].recruitProbDefWeak = 100; this->aics[aicIndex].recruitProbDefDefault = 0x22; this->aics[aicIndex].recruitProbDefStrong = 0x19; - this->aics[aicIndex].RaidUnitsRandom = 5; - this->aics[aicIndex].HarassingSiegeEnginesMax = 5; - this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x21; + this->aics[aicIndex].recruitProbRaidStrong = 0x19; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x21; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 1; this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitRanged = 0x46; this->aics[aicIndex].SortieUnitMeleeMin = 7; this->aics[aicIndex].SortieUnitMelee = 0x4b; - this->aics[aicIndex].SortieUnitRanged = 0x46; - this->aics[aicIndex].OuterPatrolGroupsMove = 1; - this->aics[aicIndex].OuterPatrolRallyDelay = 10; - this->aics[aicIndex].RaidUnitsBase = 10; - this->aics[aicIndex].AttForceRandom = 10; - this->aics[aicIndex].AttForceBase = 0x32; - this->aics[aicIndex].AttForceSupportAllyThreshold = 8; - this->aics[aicIndex].AttForceRallyPercentage = 0x50; - this->aics[aicIndex].recruitProbRaidWeak = 0; - this->aics[aicIndex].recruitProbRaidStrong = 0x19; - this->aics[aicIndex].recruitProbAttackWeak = 0; - this->aics[aicIndex].recruitProbAttackStrong = 0x32; this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefDiggingUnit = 0x47; this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x46; this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; this->aics[aicIndex].DefUnit1 = 0x4b; this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 5; this->aics[aicIndex].DefUnit3 = 0x46; this->aics[aicIndex].DefUnit4 = 0x46; this->aics[aicIndex].DefUnit5 = 0x4c; this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 3; - this->aics[aicIndex].recruitProbRaidDefault = 0x21; - this->aics[aicIndex].recruitProbAttackDefault = 0x21; - this->aics[aicIndex].DefTotal = 0x46; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 8; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; this->aics[aicIndex].AttMovingTribesThreshold = 100; this->aics[aicIndex].AttAssaultDelay = 4; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 1; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c46ee24dc..5aa1c571f 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34165,7 +34165,7 @@ SHC_3BB0A8C1_0x004C7520 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7910 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7D20 | 81.5% | Constant store order differs +SHC_3BB0A8C1_0x004C7D20 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8110 | 84.3% | Constant store order differs From ae561635c7d1eb6c3fdc7848dad4e31b0d0aedda Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:08 +0200 Subject: [PATCH 066/104] reimplement: SHC_3BB0A8C1_0x004C8110 100.00% Reimplemented --- .../AI/AICState/setAICParameters_06.cpp | 54 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_06.cpp b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp index cd71250b5..f9b06cf09 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_06.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp @@ -10,20 +10,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C8110 void AICState::setAICParameters_06(int aicIndex) { - this->aics[aicIndex].minimumCheese = -1; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; - this->aics[aicIndex].taxesMax = 0xc; - this->aics[aicIndex].farm1 = 0x20; - this->aics[aicIndex].farm3 = 0x20; - this->aics[aicIndex].unknown011 = 0xc; - this->aics[aicIndex].aiRequestDelay = 0xc; - this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x32; - this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; - this->aics[aicIndex].maxWood = 0x32; - this->aics[aicIndex].maxStone = 0x32; - this->aics[aicIndex].populationPerIronmine = 0xc; this->aics[aicIndex].flagType = 10; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -34,12 +20,25 @@ namespace AI { this->aics[aicIndex].lowestPopularity = 5000; this->aics[aicIndex].highestPopularity = 9000; this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x20; this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x20; this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xc; this->aics[aicIndex].maxFood = 100; this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x32; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; this->aics[aicIndex].maxResourceOther = 10; this->aics[aicIndex].maxEquipment = 5; this->aics[aicIndex].maxBeer = 0xf; @@ -47,6 +46,7 @@ namespace AI { this->aics[aicIndex].recruitGoldThreshold = 200; this->aics[aicIndex].populationPerWoodcutter = 7; this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; this->aics[aicIndex].populationPerPitchrig = 7; this->aics[aicIndex].maxQuarries = 2; this->aics[aicIndex].maxIronmines = 1; @@ -75,37 +75,37 @@ namespace AI { this->aics[aicIndex].sellResource15 = 0x18; this->aics[aicIndex].unknown072 = 100; this->aics[aicIndex].unknown073 = 0x14; - this->aics[aicIndex].recruitProbRaidWeak = 0x14; - this->aics[aicIndex].recruitProbAttackDefault = 0x14; - this->aics[aicIndex].recruitProbAttackWeak = 0; - this->aics[aicIndex].RecruitInterval = 0; - this->aics[aicIndex].RecruitIntervalStrong = 0; - this->aics[aicIndex].SortieUnitRangedMin = 7; - this->aics[aicIndex].SortieUnitMeleeMin = 5; - this->aics[aicIndex].SortieUnitRanged = 0x46; - this->aics[aicIndex].SortieUnitMelee = 0x4b; - this->aics[aicIndex].OuterPatrolGroupsCount = 2; - this->aics[aicIndex].HarassingSiegeEnginesMax = 2; this->aics[aicIndex].recruitProbDefWeak = 0x50; this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefStrong = 0x28; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; this->aics[aicIndex].recruitProbRaidDefault = 0x28; this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; this->aics[aicIndex].recruitProbAttackStrong = 0x1e; this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].SortieUnitRangedMin = 7; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitMelee = 0x4b; this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefDiggingUnit = 0x47; this->aics[aicIndex].DefWalls = 0x3c; this->aics[aicIndex].DefTotal = 0x3c; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 10; this->aics[aicIndex].DefUnit1 = 0x4c; this->aics[aicIndex].DefUnit2 = 0x46; - this->aics[aicIndex].DefUnit3 = 0x46; - this->aics[aicIndex].DefUnit4 = 0x46; this->aics[aicIndex].RaidUnitsBase = 0x1e; this->aics[aicIndex].RaidUnitsRandom = 0x1e; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 3; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 5aa1c571f..f0f6fdd57 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34167,7 +34167,7 @@ SHC_3BB0A8C1_0x004C7910 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C7D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8110 | 84.3% | Constant store order differs +SHC_3BB0A8C1_0x004C8110 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8530 | 94.7% | Constant store order differs From c41a4afc0d1fed56178bfa816f1d30c509ed83e8 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:09 +0200 Subject: [PATCH 067/104] reimplement: SHC_3BB0A8C1_0x004C8530 100.00% Reimplemented --- src/OpenSHC/AI/AICState/setAICParameters_07.cpp | 8 ++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_07.cpp b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp index eb1dbdaa2..101537122 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_07.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp @@ -10,10 +10,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C8530 void AICState::setAICParameters_07(int aicIndex) { - this->aics[aicIndex].minimumCheese = -1; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].flagType = 10; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -30,6 +26,10 @@ namespace AI { this->aics[aicIndex].unknown011 = 0x14; this->aics[aicIndex].maxFood = 500; this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 0; this->aics[aicIndex].aiRequestDelay = 0x1e; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f0f6fdd57..1edf7bbd2 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34169,7 +34169,7 @@ SHC_3BB0A8C1_0x004C7D20 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8530 | 94.7% | Constant store order differs +SHC_3BB0A8C1_0x004C8530 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8920 | 87.5% | Constant store order differs From 7513c072efd04cceafeee98eba523ec70037b22d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:09 +0200 Subject: [PATCH 068/104] reimplement: SHC_3BB0A8C1_0x004C8920 100.00% Reimplemented --- .../AI/AICState/setAICParameters_08.cpp | 44 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_08.cpp b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp index 020f996a2..28cef0fd5 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_08.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp @@ -9,20 +9,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C8920 void AICState::setAICParameters_08(int aicIndex) { - this->aics[aicIndex].farm2 = 0x20; - this->aics[aicIndex].farm3 = 0x20; - this->aics[aicIndex].unknown004 = 1; - this->aics[aicIndex].minimumApples = 5; - this->aics[aicIndex].minimumCheese = 5; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; - this->aics[aicIndex].tradeAmountFood = 5; - this->aics[aicIndex].maxPitchrigs = 1; this->aics[aicIndex].flagType = 0xd; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; this->aics[aicIndex].unknown005 = 0; this->aics[aicIndex].criticalPopularity = 4000; this->aics[aicIndex].lowestPopularity = 5000; @@ -30,9 +21,17 @@ namespace AI { this->aics[aicIndex].taxesMin = 3; this->aics[aicIndex].taxesMax = 10; this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x20; this->aics[aicIndex].populationPerFarm = 4; this->aics[aicIndex].unknown011 = 0xc; this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 5; this->aics[aicIndex].tradeAmountEquipment = 2; this->aics[aicIndex].aiRequestDelay = 0x16; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; @@ -51,6 +50,7 @@ namespace AI { this->aics[aicIndex].maxQuarries = 3; this->aics[aicIndex].maxIronmines = 4; this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; this->aics[aicIndex].maxFarms = 0xf; this->aics[aicIndex].buildInterval = 4; this->aics[aicIndex].resourceRebuildDelay = 0x14; @@ -74,36 +74,36 @@ namespace AI { this->aics[aicIndex].recruitProbDefWeak = 0x5a; this->aics[aicIndex].recruitProbDefDefault = 0x1e; this->aics[aicIndex].recruitProbDefStrong = 10; - this->aics[aicIndex].recruitProbAttackWeak = 10; - this->aics[aicIndex].RaidUnitsBase = 10; this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbRaidDefault = 0; this->aics[aicIndex].recruitProbRaidStrong = 0; - this->aics[aicIndex].SortieUnitRanged = 0x16; - this->aics[aicIndex].DefDiggingUnitMax = 0; - this->aics[aicIndex].DefWalls = 0x14; - this->aics[aicIndex].OuterPatrolGroupsCount = 2; - this->aics[aicIndex].DefUnit1 = 0x16; - this->aics[aicIndex].DefUnit2 = 0x16; - this->aics[aicIndex].RaidUnitsRandom = 4; - this->aics[aicIndex].RaidUnit1 = 0x16; - this->aics[aicIndex].HarassingSiegeEnginesMax = 2; - this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 10; this->aics[aicIndex].recruitProbAttackDefault = 0x46; this->aics[aicIndex].recruitProbAttackStrong = 0x5a; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 1; this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitRanged = 0x16; this->aics[aicIndex].SortieUnitMeleeMin = 6; this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnitMax = 0; this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefWalls = 0x14; this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].DefUnit3 = 0x19; + this->aics[aicIndex].RaidUnit1 = 0x16; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceBase = 0x14; this->aics[aicIndex].AttForceRandom = 5; this->aics[aicIndex].AttForceSupportAllyThreshold = 10; this->aics[aicIndex].AttForceRallyPercentage = 10; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 1edf7bbd2..1972591a6 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34171,7 +34171,7 @@ SHC_3BB0A8C1_0x004C8110 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8530 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8920 | 87.5% | Constant store order differs +SHC_3BB0A8C1_0x004C8920 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8D20 | 90.4% | Constant store order differs From 5d11984ee9ed5265ec07d3f871fd6e5f35454ee2 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:10 +0200 Subject: [PATCH 069/104] reimplement: SHC_3BB0A8C1_0x004C8D20 100.00% Reimplemented --- .../AI/AICState/setAICParameters_09.cpp | 42 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_09.cpp b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp index 3c462c06b..082d66a3a 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_09.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp @@ -9,19 +9,12 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C8D20 void AICState::setAICParameters_09(int aicIndex) { - this->aics[aicIndex].unknown004 = 1; - this->aics[aicIndex].unknown005 = 1; - this->aics[aicIndex].minimumApples = 5; - this->aics[aicIndex].minimumCheese = 5; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].minimumHop = -1; - this->aics[aicIndex].maxWood = 0x3c; - this->aics[aicIndex].maxStone = 0x3c; this->aics[aicIndex].flagType = 0xd; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; this->aics[aicIndex].criticalPopularity = 4000; this->aics[aicIndex].lowestPopularity = 0x157c; this->aics[aicIndex].highestPopularity = 9000; @@ -33,11 +26,18 @@ namespace AI { this->aics[aicIndex].populationPerFarm = 4; this->aics[aicIndex].unknown011 = 0xc; this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 0; this->aics[aicIndex].aiRequestDelay = 0x16; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; this->aics[aicIndex].maxResourceOther = 0xf; this->aics[aicIndex].maxEquipment = 0x14; this->aics[aicIndex].maxBeer = 10; @@ -74,29 +74,27 @@ namespace AI { this->aics[aicIndex].recruitProbDefStrong = 0x14; this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbRaidDefault = 0x14; - this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; - this->aics[aicIndex].RecruitInterval = 4; - this->aics[aicIndex].RecruitIntervalStrong = 4; - this->aics[aicIndex].SortieUnitRanged = 0x16; - this->aics[aicIndex].SortieUnitMeleeMin = 4; - this->aics[aicIndex].DefDiggingUnitMax = 10; - this->aics[aicIndex].DefDiggingUnit = 0x16; - this->aics[aicIndex].OuterPatrolRallyDelay = 10; - this->aics[aicIndex].DefUnit1 = 0x16; - this->aics[aicIndex].DefUnit2 = 0x16; - this->aics[aicIndex].RaidUnitsBase = 4; - this->aics[aicIndex].AttForceRandom = 4; this->aics[aicIndex].recruitProbRaidStrong = 0x1e; this->aics[aicIndex].recruitProbAttackWeak = 0; this->aics[aicIndex].recruitProbAttackDefault = 0x1e; this->aics[aicIndex].recruitProbAttackStrong = 0x32; this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; this->aics[aicIndex].SortieUnitRangedMin = 0xc; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; this->aics[aicIndex].SortieUnitMelee = 0x1b; + this->aics[aicIndex].DefDiggingUnitMax = 10; + this->aics[aicIndex].DefDiggingUnit = 0x16; this->aics[aicIndex].DefWalls = 0x32; this->aics[aicIndex].DefTotal = 0x50; this->aics[aicIndex].OuterPatrolGroupsCount = 2; this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; this->aics[aicIndex].DefUnit3 = 0x17; this->aics[aicIndex].RaidUnitsRandom = 0; this->aics[aicIndex].RaidUnit1 = 0x1c; @@ -104,6 +102,8 @@ namespace AI { this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 6; this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 4; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; this->aics[aicIndex].AttForceRallyPercentage = 0x32; this->aics[aicIndex].AttMovingTribesThreshold = 100; this->aics[aicIndex].AttAssaultDelay = 0x14; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 1972591a6..95e609fe3 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34173,7 +34173,7 @@ SHC_3BB0A8C1_0x004C8530 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8920 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8D20 | 90.4% | Constant store order differs +SHC_3BB0A8C1_0x004C8D20 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9120 | 80.0% | Constant store order differs From 08ce4047a1da2d6d1be72d411acb53dc4fb98be8 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:10 +0200 Subject: [PATCH 070/104] reimplement: SHC_3BB0A8C1_0x004C9120 100.00% Reimplemented --- .../AI/AICState/setAICParameters_10.cpp | 52 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_10.cpp b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp index 58aebe7c6..99dc276f8 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_10.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp @@ -11,8 +11,8 @@ namespace AI { void AICState::setAICParameters_10(int aicIndex) { this->aics[aicIndex].flagType = 0xd; - this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; this->aics[aicIndex].unknown004 = 1; this->aics[aicIndex].unknown005 = 1; @@ -21,21 +21,10 @@ namespace AI { this->aics[aicIndex].highestPopularity = 8000; this->aics[aicIndex].taxesMin = 2; this->aics[aicIndex].taxesMax = 10; - this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].farm1 = 0x20; this->aics[aicIndex].farm2 = 0x20; this->aics[aicIndex].populationPerFarm = 5; - this->aics[aicIndex].populationPerWoodcutter = 7; - this->aics[aicIndex].populationPerQuarry = 0; - this->aics[aicIndex].populationPerIronmine = 7; - this->aics[aicIndex].populationPerPitchrig = 0; - this->aics[aicIndex].maxQuarries = 1; - this->aics[aicIndex].maxIronmines = 3; - this->aics[aicIndex].maxWoodcutters = 10; - this->aics[aicIndex].maxPitchrigs = 1; - this->aics[aicIndex].maxFarms = 8; - this->aics[aicIndex].buildInterval = 4; - this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 100; this->aics[aicIndex].minimumApples = 10; this->aics[aicIndex].minimumCheese = 10; @@ -54,6 +43,17 @@ namespace AI { this->aics[aicIndex].maxBeer = 10; this->aics[aicIndex].maxResourceVariance = 4; this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; this->aics[aicIndex].blacksmithSetting = 0x16; this->aics[aicIndex].fletcherSetting = 0x11; this->aics[aicIndex].poleturnerSetting = 0x13; @@ -72,40 +72,40 @@ namespace AI { this->aics[aicIndex].defSiegeEngineBuildDelay = 4; this->aics[aicIndex].unknown072 = 200; this->aics[aicIndex].unknown073 = 0x14; - this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefStrong = 0x14; - this->aics[aicIndex].recruitProbRaidDefault = 0x28; this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; this->aics[aicIndex].recruitProbRaidStrong = 0x14; - this->aics[aicIndex].recruitProbAttackDefault = 0x14; this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 4; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; this->aics[aicIndex].SortieUnitRangedMin = 2; this->aics[aicIndex].SortieUnitRanged = 0x16; this->aics[aicIndex].SortieUnitMeleeMin = 0x14; this->aics[aicIndex].SortieUnitMelee = 0x18; this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefDiggingUnit = 0x18; - this->aics[aicIndex].RecruitInterval = 4; - this->aics[aicIndex].RecruitIntervalWeak = 4; - this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefWalls = 4; this->aics[aicIndex].DefTotal = 0x14; this->aics[aicIndex].OuterPatrolGroupsCount = 4; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 2; - this->aics[aicIndex].DefWalls = 4; this->aics[aicIndex].DefUnit1 = 0x1c; this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].DefUnit3 = 0x16; this->aics[aicIndex].DefUnit4 = 0x16; this->aics[aicIndex].DefUnit5 = 0x16; this->aics[aicIndex].DefUnit6 = 0x16; - this->aics[aicIndex].RaidUnitsBase = 4; - this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].RaidUnit1 = 0x1c; - this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].AttForceBase = 10; this->aics[aicIndex].AttForceRandom = 0x14; @@ -115,14 +115,14 @@ namespace AI { this->aics[aicIndex].AttAssaultDelay = 0; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; this->aics[aicIndex].AttCoordinationPreference = 0x46; - this->aics[aicIndex].SiegeEngine1 = 0; this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0; this->aics[aicIndex].AttMaxEngineers = 0; this->aics[aicIndex].AttDiggingUnit = 0x18; this->aics[aicIndex].AttDiggingUnitMax = 0x3c; + this->aics[aicIndex].AttMaxAssassins = 0; this->aics[aicIndex].AttUnit2 = 0; this->aics[aicIndex].AttUnit2Max = 0x18; - this->aics[aicIndex].AttMaxAssassins = 0; this->aics[aicIndex].AttMaxLaddermen = 0; this->aics[aicIndex].AttMaxTunnelers = 0; this->aics[aicIndex].AttUnitPatrol = 0; @@ -131,8 +131,8 @@ namespace AI { this->aics[aicIndex].AttUnitBackup = 0; this->aics[aicIndex].AttUnitBackupMax = 0; this->aics[aicIndex].AttUnitBackupGroupsCount = 0; - this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitSiegeDef = 0; this->aics[aicIndex].AttUnitSiegeDefMax = 0; this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 95e609fe3..e37bd21d5 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34175,7 +34175,7 @@ SHC_3BB0A8C1_0x004C8920 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C8D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9120 | 80.0% | Constant store order differs +SHC_3BB0A8C1_0x004C9120 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C94F0 | 89.5% | Constant store order differs From b15e74c10d89974492b678a55f048a5658f3a93c Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:10 +0200 Subject: [PATCH 071/104] reimplement: SHC_3BB0A8C1_0x004C94F0 100.00% Reimplemented --- .../AI/AICState/setAICParameters_11.cpp | 38 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_11.cpp b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp index 36c716dd8..57e5e6c3b 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_11.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp @@ -9,12 +9,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C94F0 void AICState::setAICParameters_11(int aicIndex) { - this->aics[aicIndex].taxesMin = 2; - this->aics[aicIndex].minimumApples = -1; - this->aics[aicIndex].minimumCheese = -1; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = 2; - this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].flagType = 10; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -24,11 +18,17 @@ namespace AI { this->aics[aicIndex].criticalPopularity = 0x1194; this->aics[aicIndex].lowestPopularity = 5000; this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; this->aics[aicIndex].taxesMax = 0xc; this->aics[aicIndex].farm1 = 0x1e; this->aics[aicIndex].populationPerFarm = 5; this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = -1; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = -1; this->aics[aicIndex].tradeAmountFood = 0x14; this->aics[aicIndex].tradeAmountEquipment = 0; this->aics[aicIndex].aiRequestDelay = 0x16; @@ -75,26 +75,21 @@ namespace AI { this->aics[aicIndex].recruitProbDefWeak = 0x46; this->aics[aicIndex].recruitProbDefDefault = 0x1e; this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbRaidDefault = 0x14; this->aics[aicIndex].recruitProbRaidStrong = 0x14; this->aics[aicIndex].recruitProbAttackWeak = 0x1e; - this->aics[aicIndex].DefDiggingUnitMax = 4; - this->aics[aicIndex].DefDiggingUnit = 0x47; - this->aics[aicIndex].RaidUnitsBase = 0x1e; - this->aics[aicIndex].HarassingSiegeEnginesMax = 4; - this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; - this->aics[aicIndex].RaidRetargetDelay = 4; - this->aics[aicIndex].SortieUnitMeleeMin = 10; - this->aics[aicIndex].SortieUnitRangedMin = 6; - this->aics[aicIndex].SortieUnitMelee = 0x4b; - this->aics[aicIndex].SortieUnitRanged = 0x4a; - this->aics[aicIndex].RaidUnitsRandom = 10; - this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbAttackDefault = 0x32; this->aics[aicIndex].recruitProbAttackStrong = 0x3c; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitRanged = 0x4a; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; this->aics[aicIndex].DefWalls = 0x28; this->aics[aicIndex].DefTotal = 0x28; this->aics[aicIndex].OuterPatrolGroupsCount = 1; @@ -102,9 +97,14 @@ namespace AI { this->aics[aicIndex].OuterPatrolRallyDelay = 2; this->aics[aicIndex].DefUnit1 = 0x46; this->aics[aicIndex].DefUnit2 = 0x4c; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].AttForceBase = 0x28; this->aics[aicIndex].AttForceRandom = 5; - this->aics[aicIndex].RaidUnit1 = 0x4a; this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; this->aics[aicIndex].AttForceRallyPercentage = 0x50; this->aics[aicIndex].AttMovingTribesThreshold = 100; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index e37bd21d5..ca2b62e7b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34177,7 +34177,7 @@ SHC_3BB0A8C1_0x004C8D20 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C94F0 | 89.5% | Constant store order differs +SHC_3BB0A8C1_0x004C94F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9900 | 84.2% | Constant store order differs From 3cfa10d4ae447f33ec92f699837d5b5ac0fce337 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:11 +0200 Subject: [PATCH 072/104] reimplement: SHC_3BB0A8C1_0x004C9900 100.00% Reimplemented --- .../AI/AICState/setAICParameters_12.cpp | 50 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_12.cpp b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp index c08fc909d..887cf7c84 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_12.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp @@ -11,8 +11,8 @@ namespace AI { void AICState::setAICParameters_12(int aicIndex) { this->aics[aicIndex].flagType = 10; - this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; this->aics[aicIndex].unknown004 = 1; this->aics[aicIndex].unknown005 = 1; @@ -21,23 +21,12 @@ namespace AI { this->aics[aicIndex].highestPopularity = 9000; this->aics[aicIndex].taxesMin = 2; this->aics[aicIndex].taxesMax = 0xc; - this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].farm1 = 0x1e; this->aics[aicIndex].farm2 = 0x20; this->aics[aicIndex].farm3 = 0x1e; this->aics[aicIndex].farm4 = 0x1f; this->aics[aicIndex].populationPerFarm = 4; - this->aics[aicIndex].populationPerWoodcutter = 7; - this->aics[aicIndex].populationPerQuarry = 6; - this->aics[aicIndex].populationPerIronmine = 10; - this->aics[aicIndex].populationPerPitchrig = 0; - this->aics[aicIndex].maxQuarries = 4; - this->aics[aicIndex].maxIronmines = 3; - this->aics[aicIndex].maxWoodcutters = 10; - this->aics[aicIndex].maxPitchrigs = 1; - this->aics[aicIndex].maxFarms = 0x10; - this->aics[aicIndex].buildInterval = 2; - this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 0x50; this->aics[aicIndex].minimumApples = 10; this->aics[aicIndex].minimumCheese = -1; @@ -56,6 +45,17 @@ namespace AI { this->aics[aicIndex].maxBeer = 10; this->aics[aicIndex].maxResourceVariance = 4; this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; this->aics[aicIndex].blacksmithSetting = 0x16; this->aics[aicIndex].fletcherSetting = 0x11; this->aics[aicIndex].poleturnerSetting = 0x13; @@ -72,39 +72,39 @@ namespace AI { this->aics[aicIndex].defSiegeEngineBuildDelay = 4; this->aics[aicIndex].unknown072 = 200; this->aics[aicIndex].unknown073 = 0x14; - this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefStrong = 0x14; - this->aics[aicIndex].recruitProbRaidDefault = 0x14; this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; this->aics[aicIndex].recruitProbRaidStrong = 0x1e; - this->aics[aicIndex].recruitProbAttackDefault = 0x28; this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; this->aics[aicIndex].SortieUnitRangedMin = 4; this->aics[aicIndex].SortieUnitRanged = 0x46; this->aics[aicIndex].SortieUnitMeleeMin = 0x14; this->aics[aicIndex].SortieUnitMelee = 0x4b; this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefDiggingUnit = 0x16; - this->aics[aicIndex].RecruitInterval = 0; - this->aics[aicIndex].RecruitIntervalWeak = 1; - this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].DefWalls = 0x32; this->aics[aicIndex].DefTotal = 0x32; this->aics[aicIndex].OuterPatrolGroupsCount = 1; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 2; - this->aics[aicIndex].DefWalls = 0x32; this->aics[aicIndex].DefUnit1 = 0x16; this->aics[aicIndex].DefUnit2 = 0x46; this->aics[aicIndex].RaidUnitsBase = 0x1e; this->aics[aicIndex].RaidUnitsRandom = 10; this->aics[aicIndex].RaidUnit1 = 0x4b; + this->aics[aicIndex].HarassingSiegeEnginesMax = 3; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; - this->aics[aicIndex].HarassingSiegeEngine3 = 0x166; - this->aics[aicIndex].HarassingSiegeEnginesMax = 3; this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].HarassingSiegeEngine3 = 0x166; this->aics[aicIndex].AttForceBase = 0x1e; this->aics[aicIndex].AttForceRandom = 10; this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; @@ -113,16 +113,16 @@ namespace AI { this->aics[aicIndex].AttAssaultDelay = 0; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].CowThrowInterval = 10000; this->aics[aicIndex].SiegeEngine1 = 0x27; this->aics[aicIndex].SiegeEngine2 = 0x27; this->aics[aicIndex].SiegeEngine3 = 0x3d; - this->aics[aicIndex].CowThrowInterval = 10000; this->aics[aicIndex].AttMaxEngineers = 6; this->aics[aicIndex].AttDiggingUnit = 0x16; this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttMaxAssassins = 0; this->aics[aicIndex].AttUnit2 = 0; this->aics[aicIndex].AttUnit2Max = 0; - this->aics[aicIndex].AttMaxAssassins = 0; this->aics[aicIndex].AttMaxLaddermen = 0; this->aics[aicIndex].AttMaxTunnelers = 0; this->aics[aicIndex].AttUnitPatrol = 0; @@ -131,8 +131,8 @@ namespace AI { this->aics[aicIndex].AttUnitBackup = 0x4a; this->aics[aicIndex].AttUnitBackupMax = 0x50; this->aics[aicIndex].AttUnitBackupGroupsCount = 1; - this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitSiegeDef = 0; this->aics[aicIndex].AttUnitSiegeDefMax = 0; this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index ca2b62e7b..ef7bda441 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34179,7 +34179,7 @@ SHC_3BB0A8C1_0x004C9120 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C94F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9900 | 84.2% | Constant store order differs +SHC_3BB0A8C1_0x004C9900 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9CF0 | 83.3% | Constant store order differs From 825bf1faa85ebc637d73696313b3e9c1b0dbfd15 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:11 +0200 Subject: [PATCH 073/104] reimplement: SHC_3BB0A8C1_0x004C9CF0 100.00% Reimplemented --- .../AI/AICState/setAICParameters_13.cpp | 34 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_13.cpp b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp index c4318fa1f..afea2e4b0 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_13.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp @@ -9,12 +9,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004C9CF0 void AICState::setAICParameters_13(int aicIndex) { - this->aics[aicIndex].minimumCheese = -1; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].maxFood = 100; - this->aics[aicIndex].minimumWheat = 5; - this->aics[aicIndex].minimumHop = 5; - this->aics[aicIndex].recruitGoldThreshold = 100; this->aics[aicIndex].flagType = 10; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -32,7 +26,12 @@ namespace AI { this->aics[aicIndex].farm4 = 0x1f; this->aics[aicIndex].populationPerFarm = 3; this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; this->aics[aicIndex].tradeAmountFood = 10; this->aics[aicIndex].tradeAmountEquipment = 0; this->aics[aicIndex].aiRequestDelay = 0xc; @@ -44,6 +43,7 @@ namespace AI { this->aics[aicIndex].maxEquipment = 4; this->aics[aicIndex].maxBeer = 10; this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 100; this->aics[aicIndex].populationPerWoodcutter = 7; this->aics[aicIndex].populationPerQuarry = 0x14; this->aics[aicIndex].populationPerIronmine = 10; @@ -74,39 +74,39 @@ namespace AI { this->aics[aicIndex].unknown072 = 200; this->aics[aicIndex].unknown073 = 0x14; this->aics[aicIndex].recruitProbDefWeak = 0x50; - this->aics[aicIndex].recruitProbRaidStrong = 0x1e; - this->aics[aicIndex].recruitProbAttackDefault = 0x1e; - this->aics[aicIndex].RecruitIntervalStrong = 1; - this->aics[aicIndex].OuterPatrolGroupsCount = 1; - this->aics[aicIndex].OuterPatrolGroupsMove = 1; - this->aics[aicIndex].RecruitInterval = 4; - this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].recruitProbDefDefault = 0x32; this->aics[aicIndex].recruitProbDefStrong = 0x14; this->aics[aicIndex].recruitProbRaidWeak = 0; this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; this->aics[aicIndex].recruitProbAttackStrong = 0x32; this->aics[aicIndex].RecruitIntervalWeak = 8; - this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 1; this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x46; this->aics[aicIndex].SortieUnitMeleeMin = 0xf; this->aics[aicIndex].SortieUnitMelee = 0x46; this->aics[aicIndex].DefDiggingUnitMax = 0x3c; this->aics[aicIndex].DefDiggingUnit = 0x47; this->aics[aicIndex].DefWalls = 0x50; this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 2; this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x49; this->aics[aicIndex].HarassingSiegeEnginesMax = 0; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].AttForceBase = 0x28; this->aics[aicIndex].AttForceRandom = 0; this->aics[aicIndex].AttForceSupportAllyThreshold = 10; this->aics[aicIndex].AttForceRallyPercentage = 0x32; - this->aics[aicIndex].RaidUnitsBase = 5; - this->aics[aicIndex].RaidUnitsRandom = 5; - this->aics[aicIndex].RaidUnit1 = 0x49; this->aics[aicIndex].AttMovingTribesThreshold = 100; this->aics[aicIndex].AttAssaultDelay = 0; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index ef7bda441..0d705abea 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34181,7 +34181,7 @@ SHC_3BB0A8C1_0x004C94F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9900 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9CF0 | 83.3% | Constant store order differs +SHC_3BB0A8C1_0x004C9CF0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CA0C0 | 82.3% | Constant store order differs From 42e50355913f8594fa16906d84eea944a4f72a89 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:12 +0200 Subject: [PATCH 074/104] reimplement: SHC_3BB0A8C1_0x004CA0C0 100.00% Reimplemented --- .../AI/AICState/setAICParameters_14.cpp | 52 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_14.cpp b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp index bee77c542..64c2099b7 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_14.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp @@ -11,8 +11,8 @@ namespace AI { void AICState::setAICParameters_14(int aicIndex) { this->aics[aicIndex].flagType = 0xd; - this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; this->aics[aicIndex].unknown003 = 0; this->aics[aicIndex].unknown004 = 1; this->aics[aicIndex].unknown005 = 1; @@ -21,22 +21,11 @@ namespace AI { this->aics[aicIndex].highestPopularity = 9000; this->aics[aicIndex].taxesMin = 3; this->aics[aicIndex].taxesMax = 0xb; - this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].farm1 = 0x20; this->aics[aicIndex].farm2 = 0x1f; this->aics[aicIndex].farm3 = 0x21; this->aics[aicIndex].populationPerFarm = 4; - this->aics[aicIndex].populationPerWoodcutter = 7; - this->aics[aicIndex].populationPerQuarry = 10; - this->aics[aicIndex].populationPerIronmine = 0xc; - this->aics[aicIndex].populationPerPitchrig = 0xf; - this->aics[aicIndex].maxQuarries = 3; - this->aics[aicIndex].maxIronmines = 6; - this->aics[aicIndex].maxWoodcutters = 10; - this->aics[aicIndex].maxPitchrigs = 4; - this->aics[aicIndex].maxFarms = 10; - this->aics[aicIndex].buildInterval = 3; - this->aics[aicIndex].resourceRebuildDelay = 8; + this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 300; this->aics[aicIndex].minimumApples = 0x14; this->aics[aicIndex].minimumCheese = 0x14; @@ -55,6 +44,17 @@ namespace AI { this->aics[aicIndex].maxBeer = 10; this->aics[aicIndex].maxResourceVariance = 5; this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 6; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 8; this->aics[aicIndex].blacksmithSetting = 0x15; this->aics[aicIndex].fletcherSetting = 0x12; this->aics[aicIndex].poleturnerSetting = 0x14; @@ -71,40 +71,40 @@ namespace AI { this->aics[aicIndex].defSiegeEngineBuildDelay = 4; this->aics[aicIndex].unknown072 = 200; this->aics[aicIndex].unknown073 = 0x1e; - this->aics[aicIndex].recruitProbDefDefault = 0x32; this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x32; this->aics[aicIndex].recruitProbDefStrong = 0x1e; - this->aics[aicIndex].recruitProbRaidDefault = 0x1e; this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; this->aics[aicIndex].recruitProbRaidStrong = 0x32; - this->aics[aicIndex].recruitProbAttackDefault = 0x14; this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; this->aics[aicIndex].recruitProbAttackStrong = 0x14; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; this->aics[aicIndex].SortieUnitRangedMin = 10; this->aics[aicIndex].SortieUnitRanged = 0x17; this->aics[aicIndex].SortieUnitMeleeMin = 10; this->aics[aicIndex].SortieUnitMelee = 0x1a; this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefDiggingUnit = 0x1a; - this->aics[aicIndex].RecruitInterval = 4; - this->aics[aicIndex].RecruitIntervalWeak = 1; - this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].DefWalls = 0x28; this->aics[aicIndex].DefTotal = 0x32; this->aics[aicIndex].OuterPatrolGroupsCount = 1; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 5; - this->aics[aicIndex].DefWalls = 0x28; this->aics[aicIndex].DefUnit1 = 0x17; this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].DefUnit3 = 0x17; this->aics[aicIndex].DefUnit4 = 0x4c; this->aics[aicIndex].DefUnit5 = 0x49; - this->aics[aicIndex].RaidUnitsBase = 10; - this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].HarassingSiegeEngine2 = 0x166; - this->aics[aicIndex].HarassingSiegeEnginesMax = 5; this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].AttForceBase = 0x32; this->aics[aicIndex].AttForceRandom = 0x14; @@ -114,18 +114,18 @@ namespace AI { this->aics[aicIndex].AttAssaultDelay = 5; this->aics[aicIndex].AttUnitPatrolRecommandDelay = 8; this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 6; this->aics[aicIndex].SiegeEngine1 = 0x27; this->aics[aicIndex].SiegeEngine2 = 0x27; this->aics[aicIndex].SiegeEngine3 = 0x4d; this->aics[aicIndex].SiegeEngine4 = 0x4d; this->aics[aicIndex].SiegeEngine5 = 0x4d; - this->aics[aicIndex].CowThrowInterval = 6; this->aics[aicIndex].AttMaxEngineers = 10; this->aics[aicIndex].AttDiggingUnit = 0x47; this->aics[aicIndex].AttDiggingUnitMax = 0x50; + this->aics[aicIndex].AttMaxAssassins = 10; this->aics[aicIndex].AttUnit2 = 0x1a; this->aics[aicIndex].AttUnit2Max = 10; - this->aics[aicIndex].AttMaxAssassins = 10; this->aics[aicIndex].AttMaxLaddermen = 0; this->aics[aicIndex].AttMaxTunnelers = 0; this->aics[aicIndex].AttUnitPatrol = 0x4c; @@ -134,8 +134,8 @@ namespace AI { this->aics[aicIndex].AttUnitBackup = 0x17; this->aics[aicIndex].AttUnitBackupMax = 0x32; this->aics[aicIndex].AttUnitBackupGroupsCount = 1; - this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; this->aics[aicIndex].AttUnitSiegeDef = 0x1a; this->aics[aicIndex].AttUnitSiegeDefMax = 6; this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 0d705abea..899c814e2 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34183,7 +34183,7 @@ SHC_3BB0A8C1_0x004C9900 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004C9CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA0C0 | 82.3% | Constant store order differs +SHC_3BB0A8C1_0x004CA0C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CA4D0 | 74.9% | Constant store order differs From 21273393fdb0254037795ce780b06bc189fcf20e Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:12 +0200 Subject: [PATCH 075/104] reimplement: SHC_3BB0A8C1_0x004CA4D0 100.00% Reimplemented --- .../AI/AICState/setAICParameters_15.cpp | 58 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_15.cpp b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp index 2d2c9deca..856ba4069 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_15.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp @@ -9,20 +9,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CA4D0 void AICState::setAICParameters_15(int aicIndex) { - this->aics[aicIndex].farm1 = 0x21; - this->aics[aicIndex].farm3 = 0x21; - this->aics[aicIndex].farm4 = 0x21; - this->aics[aicIndex].populationPerFarm = 5; - this->aics[aicIndex].minimumHop = 5; - this->aics[aicIndex].minimumBread = -1; - this->aics[aicIndex].minimumWheat = -1; - this->aics[aicIndex].populationPerWoodcutter = 7; - this->aics[aicIndex].populationPerQuarry = 7; - this->aics[aicIndex].populationPerIronmine = 7; - this->aics[aicIndex].aiRequestDelay = 0xc; - this->aics[aicIndex].maxResourceOther = 0xc; - this->aics[aicIndex].maxIronmines = 3; - this->aics[aicIndex].buildInterval = 3; this->aics[aicIndex].flagType = 0xd; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -34,26 +20,40 @@ namespace AI { this->aics[aicIndex].highestPopularity = 8000; this->aics[aicIndex].taxesMin = 2; this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].farm4 = 0x21; + this->aics[aicIndex].populationPerFarm = 5; this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 100; this->aics[aicIndex].minimumApples = 10; this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = 5; this->aics[aicIndex].tradeAmountFood = 0x14; this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; this->aics[aicIndex].maxWood = 0x3c; this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 0xc; this->aics[aicIndex].maxEquipment = 10; this->aics[aicIndex].maxBeer = 6; this->aics[aicIndex].maxResourceVariance = 4; this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 7; + this->aics[aicIndex].populationPerIronmine = 7; this->aics[aicIndex].populationPerPitchrig = 0; this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; this->aics[aicIndex].maxWoodcutters = 8; this->aics[aicIndex].maxPitchrigs = 1; this->aics[aicIndex].maxFarms = 9; + this->aics[aicIndex].buildInterval = 3; this->aics[aicIndex].resourceRebuildDelay = 0x28; this->aics[aicIndex].blacksmithSetting = 0x16; this->aics[aicIndex].fletcherSetting = 0x11; @@ -71,40 +71,40 @@ namespace AI { this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; this->aics[aicIndex].defSiegeEngineBuildDelay = 4; this->aics[aicIndex].unknown072 = 200; - this->aics[aicIndex].RecruitIntervalStrong = 4; - this->aics[aicIndex].SortieUnitRangedMin = 4; - this->aics[aicIndex].SortieUnitMeleeMin = 4; - this->aics[aicIndex].DefDiggingUnitMax = 4; - this->aics[aicIndex].RaidUnitsRandom = 4; - this->aics[aicIndex].RaidRetargetDelay = 4; - this->aics[aicIndex].recruitProbRaidWeak = 0x28; - this->aics[aicIndex].recruitProbRaidDefault = 0x28; - this->aics[aicIndex].recruitProbAttackWeak = 10; - this->aics[aicIndex].SortieUnitRanged = 0x16; - this->aics[aicIndex].DefDiggingUnit = 0x16; - this->aics[aicIndex].DefTotal = 0x28; - this->aics[aicIndex].DefUnit1 = 0x16; - this->aics[aicIndex].AttForceBase = 10; - this->aics[aicIndex].AttForceSupportAllyThreshold = 10; this->aics[aicIndex].unknown073 = 0x14; this->aics[aicIndex].recruitProbDefWeak = 0x32; this->aics[aicIndex].recruitProbDefDefault = 0x1e; this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; this->aics[aicIndex].recruitProbAttackDefault = 0x1e; this->aics[aicIndex].recruitProbAttackStrong = 0x32; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; this->aics[aicIndex].SortieUnitMelee = 0x1c; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x16; this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x28; this->aics[aicIndex].OuterPatrolGroupsCount = 1; this->aics[aicIndex].OuterPatrolGroupsMove = 1; this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; this->aics[aicIndex].RaidUnitsBase = 0x10; + this->aics[aicIndex].RaidUnitsRandom = 4; this->aics[aicIndex].RaidUnit1 = 0x1c; this->aics[aicIndex].HarassingSiegeEnginesMax = 1; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 10; this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; this->aics[aicIndex].AttForceRallyPercentage = 0x32; this->aics[aicIndex].AttMovingTribesThreshold = 100; this->aics[aicIndex].AttAssaultDelay = 0; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 899c814e2..29e7598e3 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34185,7 +34185,7 @@ SHC_3BB0A8C1_0x004C9CF0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CA0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA4D0 | 74.9% | Constant store order differs +SHC_3BB0A8C1_0x004CA4D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CA8A0 | 84.8% | Constant store order differs From e2e695146777f5c0c18ede9369abebe303989aa7 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 21:12:12 +0200 Subject: [PATCH 076/104] reimplement: SHC_3BB0A8C1_0x004CA8A0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/setAICParameters_16.cpp | 16 ++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OpenSHC/AI/AICState/setAICParameters_16.cpp b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp index 1721cd187..2ce895b50 100644 --- a/src/OpenSHC/AI/AICState/setAICParameters_16.cpp +++ b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp @@ -10,11 +10,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CA8A0 void AICState::setAICParameters_16(int aicIndex) { - this->aics[aicIndex].taxesMax = 0xc; - this->aics[aicIndex].farm2 = 0x1e; - this->aics[aicIndex].farm3 = 0x1e; - this->aics[aicIndex].aiRequestDelay = 0xc; - this->aics[aicIndex].maxFarms = 0xc; this->aics[aicIndex].flagType = 0xd; this->aics[aicIndex].unknown002 = 0; this->aics[aicIndex].unknown001 = 0; @@ -25,7 +20,10 @@ namespace AI { this->aics[aicIndex].lowestPopularity = 5000; this->aics[aicIndex].highestPopularity = 9000; this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; this->aics[aicIndex].populationPerFarm = 4; this->aics[aicIndex].unknown011 = 10; this->aics[aicIndex].maxFood = 100; @@ -36,6 +34,7 @@ namespace AI { this->aics[aicIndex].minimumHop = 2; this->aics[aicIndex].tradeAmountFood = 5; this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; this->aics[aicIndex].maxWood = 0x3c; @@ -53,6 +52,7 @@ namespace AI { this->aics[aicIndex].maxIronmines = 2; this->aics[aicIndex].maxWoodcutters = 10; this->aics[aicIndex].maxPitchrigs = 0; + this->aics[aicIndex].maxFarms = 0xc; this->aics[aicIndex].buildInterval = 2; this->aics[aicIndex].resourceRebuildDelay = 0x28; this->aics[aicIndex].blacksmithSetting = 0x16; @@ -76,9 +76,6 @@ namespace AI { this->aics[aicIndex].recruitProbDefDefault = 0x28; this->aics[aicIndex].recruitProbDefStrong = 0x14; this->aics[aicIndex].recruitProbRaidWeak = 10; - this->aics[aicIndex].SortieUnitRangedMin = 4; - this->aics[aicIndex].DefDiggingUnitMax = 4; - this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].RecruitIntervalWeak = 1; this->aics[aicIndex].RecruitInterval = 1; this->aics[aicIndex].RecruitIntervalStrong = 1; @@ -93,14 +90,17 @@ namespace AI { this->aics[aicIndex].recruitProbAttackWeak = 10; this->aics[aicIndex].recruitProbAttackDefault = 0x28; this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitRangedMin = 4; this->aics[aicIndex].SortieUnitMeleeMin = 0x14; this->aics[aicIndex].SortieUnitMelee = 0x25; + this->aics[aicIndex].DefDiggingUnitMax = 4; this->aics[aicIndex].DefWalls = 0x1e; this->aics[aicIndex].DefTotal = 0x1e; this->aics[aicIndex].RaidUnitsBase = 0x1e; this->aics[aicIndex].RaidUnitsRandom = 10; this->aics[aicIndex].RaidUnit1 = 0x25; this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].RaidRetargetDelay = 4; this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; this->aics[aicIndex].AttForceBase = 0x32; this->aics[aicIndex].AttForceRandom = 10; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 29e7598e3..b4fcf174b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34187,7 +34187,7 @@ SHC_3BB0A8C1_0x004CA0C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CA4D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA8A0 | 84.8% | Constant store order differs +SHC_3BB0A8C1_0x004CA8A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CAC70 | 95.2% | Reimplemented From fc674a09cb0780c071d11549b499833636a3efbc Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:45 +0200 Subject: [PATCH 077/104] reimplement: SHC_3BB0A8C1_0x004CCD20 66.03% frame and regalloc differ --- .../addUnitToSmallestBehaviourTypeTribe.cpp | 15 +++++++++------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp index 50144e366..5d3e5ddf3 100644 --- a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -18,9 +18,8 @@ namespace AI { return 0; int aicIndex = aiType - 1; - int tribeType = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; int groupsCount; - switch (tribeType) { + switch (DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]) { case 15: groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; break; @@ -41,17 +40,21 @@ namespace AI { int selectedTribeID = 0; int selectedSlot = 0; if (groupsCount <= 1) { - int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType]; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]; if (tribeID != 0 && DAT_TribesState::instance.tribes[tribeID].uid - == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeType]) + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]) return tribeID; selectedTribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); - selectedSlot = tribeType; + selectedSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; } else { int smallestSize = 1000; - for (int slot = tribeType; slot < tribeType + groupsCount; slot++) { + for (int slot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; slot + < DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10] + groupsCount; + slot++) { int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[slot]; if (tribeID == 0 || DAT_TribesState::instance.tribes[tribeID].uid diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b4fcf174b..2f39665cd 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34893,7 +34893,7 @@ SHC_3BB0A8C1_0x004CCAF0 | 68.1% | Register allocation differs SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 63.9% | Loop register allocation diff +SHC_3BB0A8C1_0x004CCD20 | 66.0% | frame and regalloc differ SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending From a2aa4604130c1d48f7c919989ef39133ad309f2b Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:45 +0200 Subject: [PATCH 078/104] reimplement: SHC_3BB0A8C1_0x004CF2E0 94.74% esi/edi regalloc differs --- src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp | 3 +-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp index 0c086b105..818b68d3c 100644 --- a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -33,11 +33,10 @@ namespace AI { MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, DAT_PathFindingState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, DAT_UnitsState::instance.units[targetUnitID].y, -1, -1, 10000, FALSE); - int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, - DAT_PathFindingState::ptr)(attackedPlayerID, 200) + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 200) == FALSE && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 2f39665cd..744e48563 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35355,7 +35355,7 @@ SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF2E0 | 83.4% | esi/edi regalloc differs +SHC_3BB0A8C1_0x004CF2E0 | 94.7% | esi/edi regalloc differs SHC_3BB0A8C1_0x004CF400 | 100.0% | Reimplemented From 5c3a5d173dd2605314d7427664c72a1db225e4e0 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:45 +0200 Subject: [PATCH 079/104] reimplement: SHC_3BB0A8C1_0x004D2000 39.43% frame size and regalloc differ --- .../AI/AICState/aiCreateSiegeUnits.cpp | 27 +++++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index d1cac0ab6..d82382723 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -44,20 +44,17 @@ namespace AI { return; for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { - UnitType unitType - = (UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].unitType; - int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; - int locationCount - = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; if (locationCount >= 4) locationCount = 3; for (int i = 0; i < locationCount; i++) { - int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i]; + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot][i]; if (location <= 0) continue; - int existingBuildingID = (short)DAT_TileMapState::instance.BuildingLayer[location]; int unitID = DAT_GameState::instance.playerDataArray[playerID] .aiSiegeCreationInformation[siegeIndex][i] .unitID; @@ -120,15 +117,19 @@ namespace AI { return; } - if (existingBuildingID == 0) { + if (((short)DAT_TileMapState::instance.BuildingLayer[location]) == 0) { if (siegeIndex < 2) continue; } else { if (siegeIndex >= 2) continue; - if (DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + if (DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType != OpenSHC::Map::Buildings::BT_TOWER4 - && DAT_BuildingsState::instance.buildings[existingBuildingID].buildingType + && DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType != OpenSHC::Map::Buildings::BT_TOWER5) continue; } @@ -154,7 +155,11 @@ namespace AI { if (siegeIndex < 2) { int spawnedUnitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::spawnUnit, DAT_UnitsState::ptr)(playerID, playerID, x * 8 + 4, y * 8 + 4, - DAT_BuildingsState::instance.buildings[existingBuildingID].terrainHeightUnk, unitType); + DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .terrainHeightUnk, + ((UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .unitType)); DAT_GameState::instance.playerDataArray[playerID] .aiSiegeCreationInformation[siegeIndex][i] .unitID = spawnedUnitID; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 744e48563..b7ad7344c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 34.9% | fixed siegeIndex>=2 skip; fram +SHC_3BB0A8C1_0x004D2000 | 39.4% | frame size and regalloc differ SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From f90e6a530defcf0d4325fd80e970fd87a309425e Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:46 +0200 Subject: [PATCH 080/104] reimplement: SHC_3BB0A8C1_0x004D3AE0 41.60% regalloc differs --- src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 14 ++++++++------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp index 46b4dba99..4cf7a40a3 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -110,9 +110,10 @@ namespace AI { .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] == 0) DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; - int choice = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex; - unitType = (UnitType)(&this->aics[aicIndex].DefUnit1)[choice]; - DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = choice + 1; + unitType = (UnitType)(&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + 1; } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_RAIDING) { if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) @@ -121,9 +122,10 @@ namespace AI { .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] == 0) DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; - int choice = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex; - unitType = (UnitType)(&this->aics[aicIndex].RaidUnit1)[choice]; - DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = choice + 1; + unitType = (UnitType)(&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + 1; } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState == OpenSHC::AI::AIRUC_ATTACKING) { behaviourType = MACRO_CALL_MEMBER( diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b7ad7344c..c4eb526d5 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35527,7 +35527,7 @@ SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 40.3% | pointer-walk/regalloc differs +SHC_3BB0A8C1_0x004D3AE0 | 41.6% | regalloc differs SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented From bf2ba81321de484a5144074f9ce57a4ced61103d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:46 +0200 Subject: [PATCH 081/104] reimplement: SHC_3BB0A8C1_0x004CDB20 44.44% regalloc/spill differs --- ...andomBuildingIDFromPlayersArrayOfBuildings.cpp | 15 ++++++++------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp index a54e7c265..94ed39aaf 100644 --- a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -13,23 +13,24 @@ namespace AI { { int chosenBuildingID = 0; int chosenPriority = 46; - int buildingCount = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; - int rng = SEC_RNG::instance.currentNumber2 & 7; - for (int i = 0; i < buildingCount; i++) { + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int rng = SEC_RNG::instance.currentNumber2 & 7; int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; if (buildingID == 0) continue; - int buildingType = DAT_BuildingsState::instance.buildings[buildingID].buildingType; for (int j = 0; j < chosenPriority; j++) { if (rng <= 1) { - if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]) + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]) continue; } else if (rng <= 3) { - if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]) + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]) continue; } else { - if (buildingType != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) continue; } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c4eb526d5..7ff123447 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35287,7 +35287,7 @@ SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 34.3% | regalloc/spill differs +SHC_3BB0A8C1_0x004CDB20 | 44.4% | regalloc/spill differs SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented From 007ee42f6612b9534afe7c6b4f5df0ca77e3450d Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:47 +0200 Subject: [PATCH 082/104] reimplement: SHC_3BB0A8C1_0x004CE200 64.52% spilled local differs --- src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp | 4 ++-- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp index d158bc23f..375733aef 100644 --- a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -20,7 +20,6 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 void AICState::computeEnemyKeepApproachTile(int playerID) { - int keepX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; int keepY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; this->aiBorderTilesIndex = 0; MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( @@ -30,7 +29,8 @@ namespace AI { for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { int tile = this->aiBorderTiles[i].tile; MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, - DAT_DirectionAlgorithmState::ptr)(keepX, keepY, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + keepY, tile - DAT_ViewportRenderState::instance .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7ff123447..f00b8f0c9 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35307,7 +35307,7 @@ SHC_3BB0A8C1_0x004CE110 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE1A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE200 | 55.3% | Register/store scheduling diff +SHC_3BB0A8C1_0x004CE200 | 64.5% | spilled local differs SHC_3BB0A8C1_0x004CE270 | 0.0% | Pending From 5f8b29ab6a258904e70f1b812c80ee389eb5f499 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:47 +0200 Subject: [PATCH 083/104] reimplement: SHC_3BB0A8C1_0x004CCAF0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp | 12 ++++++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp index cea616412..09ce8c0d5 100644 --- a/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp +++ b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp @@ -11,11 +11,18 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CCAF0 int AICState::getSmallestPatrolTribe(int playerID, int tribeCount) { + int i = 0; int selectedTribeID = 0; int selectedIndex = 0; int lowestSize = 1000; - for (int i = 0; i < tribeCount; i++) { + for (;; i++) { + if (i >= tribeCount) { + if (selectedTribeID == 0) + return 0; + break; + } + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; if (tribeID == 0 || DAT_TribesState::instance.tribes[tribeID].uid @@ -33,9 +40,6 @@ namespace AI { } } - if (selectedTribeID == 0) - return 0; - DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedIndex + 170] = selectedTribeID; DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedIndex + 170] = DAT_TribesState::instance.tribes[selectedTribeID].uid; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f00b8f0c9..1c101c577 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34889,7 +34889,7 @@ SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented SHC_3BB0A8C1_0x004CC990 | 40.90% | Reimplemented functionally, different register usage -SHC_3BB0A8C1_0x004CCAF0 | 68.1% | Register allocation differs +SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented From 2477b7d7132bce0bce3cb33d138966a63a5d3971 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:47 +0200 Subject: [PATCH 084/104] reimplement: SHC_3BB0A8C1_0x004D1320 72.34% regalloc differs --- src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp | 9 ++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp index 949062c85..a54ab0211 100644 --- a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp +++ b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp @@ -47,7 +47,6 @@ namespace AI { continue; } - int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; uint color = DAT_RenderingDefinedData::instance .ColorArray[DAT_BlendingDefinedData::instance.PlayerSlotUnitColor[playerID]]; @@ -102,7 +101,7 @@ namespace AI { MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "E:%d/%d", DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6, DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 - + this->aics[aicIndex].SortieUnitRangedMin); + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].SortieUnitRangedMin); MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( text, x + 452, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); @@ -118,7 +117,7 @@ namespace AI { } else { MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Sg:%d/%d", siegeTroops, DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength - + this->aics[aicIndex].AttForceBase); + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].AttForceBase); MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); } @@ -127,14 +126,14 @@ namespace AI { MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Hr:%d/%d", DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)( - aicIndex, playerID)); + (DAT_GameState::instance.playerDataArray[playerID].aiType - 1), playerID)); MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( text, x + 592, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); // Defensive troops MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Df:%d/%d", DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk, - this->aics[aicIndex].DefWalls); + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].DefWalls); MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( text, x + 657, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 1c101c577..521f67a6c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35463,7 +35463,7 @@ SHC_3BB0A8C1_0x004D11C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D12A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1320 | 69.88% | Partially reimplemented, stuck +SHC_3BB0A8C1_0x004D1320 | 72.3% | regalloc differs SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending From d35fab3dc2281242a82572c5410471c7133adb24 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:48 +0200 Subject: [PATCH 085/104] reimplement: SHC_3BB0A8C1_0x004D1D60 62.41% register permutation --- src/OpenSHC/AI/AICState/sellExcessGoods.cpp | 32 +++++++++++++-------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp index 222bd7854..038b44af9 100644 --- a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp +++ b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp @@ -33,35 +33,41 @@ namespace AI { int aicIndex = aiType - 1; int playerResourceBase = playerID * (sizeof(PlayerData) / sizeof(int)); - int nervousActions = DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker; for (int i = 0; i < 20; i++) { ResourceType resourceType = (ResourceType)DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].game; - int category = DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic; int variance = this->aics[aicIndex].maxResourceVariance; int resourceOffset = (playerResourceBase + resourceType) * sizeof(int); int amount; - if (category == OpenSHC::AI::Trading::AIRTC_WOOD) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WOOD) { amount = this->aics[aicIndex].maxWood; - } else if (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { amount = this->aics[aicIndex].maxFood; - } else if (category == OpenSHC::AI::Trading::AIRTC_STONE) { + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_STONE) { amount = this->aics[aicIndex].maxStone; - } else if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { amount = this->aics[aicIndex].maxEquipment; - } else if (category == OpenSHC::AI::Trading::AIRTC_ALE) { + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_ALE) { amount = this->aics[aicIndex].maxBeer; } else { amount = this->aics[aicIndex].maxResourceOther; } amount += variance; - if (nervousActions > 0 && DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf] < 500) { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf] < 500) { // Nervous and low on gold: keep a quarter of the food and up to 10000 weapons, sell everything else - if (category == OpenSHC::AI::Trading::AIRTC_FOOD) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { amount = amount / 4; - } else if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { amount = 10000; } else { amount = 0; @@ -70,8 +76,10 @@ namespace AI { } else if (DAT_GameState::instance.playerDataArray[playerID].popularity < this->aics[aicIndex].criticalPopularity) { // Critical popularity: keep all food, sell all weapons, keep a quarter of everything else - if (category != OpenSHC::AI::Trading::AIRTC_FOOD) { - if (category == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + != OpenSHC::AI::Trading::AIRTC_FOOD) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { amount = 0; } else { amount = amount / 4; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 521f67a6c..89e4d2d6b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35475,7 +35475,7 @@ SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1D60 | 58.0% | Register allocation differs +SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented From c71a8478faed8224d2dd2d8a4622c72ab353c1f0 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:48 +0200 Subject: [PATCH 086/104] reimplement: SHC_3BB0A8C1_0x004CC990 56.59% regalloc/locals differ --- .../AI/AICState/smallestTribeOfUnitType.cpp | 34 ++++++++----------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp index c626ef572..ce194bb2d 100644 --- a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -18,30 +18,30 @@ namespace AI { int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + int smallestTribeID = 0; + int smallestIndex = 0; + int smallestSize = 1000; if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { count = maxGroups; } - int smallestTribeID = 0; - int smallestIndex = 0; - int smallestSize = 1000; + int i = 0; int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; - for (int i = 0; i < count; i++) { + for (;; i++) { + if (i >= count) { + if (smallestTribeID == 0) + return 0; + break; + } + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i]; if (tribeID == 0 || DAT_TribesState::instance.tribes[tribeID].uid != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i]) { - // FIXME: The original binary jumps from here directly into the shared assignment code at the - // end of the function, skipping the "smallestTribeID == 0" check. A goto reproduces this and - // matches the assembly much better (~55% vs ~41%), but we avoid goto, so the assignment is - // duplicated here. Do not "simplify" this into a break into the shared tail below, the way - // addUnitToSmallestBehaviourTypeTribe is written: that was measured at 17%. - tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)( - playerID); - DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i] = (short)tribeID; - DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i] - = DAT_TribesState::instance.tribes[tribeID].uid; - return tribeID; + smallestTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + smallestIndex = i; + break; } if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { smallestSize = DAT_TribesState::instance.tribes[tribeID].size; @@ -50,10 +50,6 @@ namespace AI { } } - if (smallestTribeID == 0) { - return 0; - } - DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + smallestIndex] = (short)smallestTribeID; DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + smallestIndex] = DAT_TribesState::instance.tribes[smallestTribeID].uid; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 89e4d2d6b..c6dcf109b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34887,7 +34887,7 @@ SHC_3BB0A8C1_0x004CC8D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented -SHC_3BB0A8C1_0x004CC990 | 40.90% | Reimplemented functionally, different register usage +SHC_3BB0A8C1_0x004CC990 | 56.6% | regalloc/locals differ SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented From a6678bff0b9c10ea40c93dcf32103d6f918c533c Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:06:49 +0200 Subject: [PATCH 087/104] reimplement: SHC_3BB0A8C1_0x004D3320 97.24% ebp/ebx regalloc differs --- src/OpenSHC/AI/AICState/updateAIStrengthState.cpp | 10 ++++++---- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp index 249bea57d..f302283a6 100644 --- a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp +++ b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp @@ -92,10 +92,12 @@ namespace AI { DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; // the three recruitProb* triples are laid out as Default, Weak, Strong, matching AIStrengthType - int _aiStrengthFeeling = DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling; - int _recruitProbDefensive = (&this->aics[_aicIndex].recruitProbDefDefault)[_aiStrengthFeeling]; - int _recruitProbAttack = (&this->aics[_aicIndex].recruitProbAttackDefault)[_aiStrengthFeeling]; - int _recruitProbRaid = (&this->aics[_aicIndex].recruitProbRaidDefault)[_aiStrengthFeeling]; + int _recruitProbDefensive = (&this->aics[_aicIndex] + .recruitProbDefDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbAttack = (&this->aics[_aicIndex] + .recruitProbAttackDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbRaid = (&this->aics[_aicIndex] + .recruitProbRaidDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; int _totalRecruitProb = _recruitProbAttack + _recruitProbRaid + _recruitProbDefensive; int _aiRecruitUnitChoice; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index c6dcf109b..cff41f765 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35511,7 +35511,7 @@ SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3320 | 87.69% | Reimplemented, different register usage, effectively 100% +SHC_3BB0A8C1_0x004D3320 | 97.2% | ebp/ebx regalloc differs SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented From fcfabd0b9e134495b00206094cc4cd01cdb5658b Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:22:29 +0200 Subject: [PATCH 088/104] reimplement: SHC_3BB0A8C1_0x004D28B0 86.32% select call block layout diffe --- .../AI/AICState/aiGiveOuterPatrolCommand.cpp | 31 +++++++++++++------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp index 1e8ad2f21..218454530 100644 --- a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -18,6 +18,7 @@ namespace AI { if (aiType == 0) return; int aicIndex = aiType - 1; + int i = 0; int groupsCount = this->aics[aicIndex].OuterPatrolGroupsCount; if (groupsCount <= 0) return; @@ -28,7 +29,7 @@ namespace AI { return; DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker = 0; - for (int i = 0; i < groupsCount; i++) { + for (; i < groupsCount; i++) { int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; if (tribeID == 0) @@ -43,17 +44,27 @@ namespace AI { if (DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10) continue; - int buildingID = 0; - if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) - buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); int tile; - if (buildingID != 0) { - tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX - + DAT_ViewportRenderState::instance - .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] - .addXgetTile; + if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) { + int buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); + if (buildingID != 0) { + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } } else { - buildingID = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + int buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); if (buildingID == 0) continue; tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index cff41f765..47f35f855 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35499,7 +35499,7 @@ SHC_3BB0A8C1_0x004D2830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D28B0 | 73.0% | Register allocation differs +SHC_3BB0A8C1_0x004D28B0 | 86.3% | select call block layout diffe SHC_3BB0A8C1_0x004D2A70 | 30.0% | Register allocation differs From 001f2045bcd00e9f0f4f3bf1fd7bd82ca83c7d42 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:22:29 +0200 Subject: [PATCH 089/104] reimplement: SHC_3BB0A8C1_0x004D2660 100.00% Reimplemented --- src/OpenSHC/AI/AICState/assignUnitToATribe.cpp | 18 ++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp index 42fac935e..547e164cf 100644 --- a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp +++ b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp @@ -26,18 +26,16 @@ namespace AI { else aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, 0); DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 1; + int tribeID; if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlotLocationCount[aivUnitType] <= 0) { - if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) { - MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( - unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 0)); - return; - } - MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( - unitID, MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 1)); - return; + if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) + tribeID = MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 0); + else + tribeID = MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 1); + } else { + tribeID = MACRO_CALL_MEMBER(AICState_Func::smallestTribeOfUnitType, this)(owner, aivUnitType); } - MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( - unitID, MACRO_CALL_MEMBER(AICState_Func::smallestTribeOfUnitType, this)(owner, aivUnitType)); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); } } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 47f35f855..a489fce92 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35487,7 +35487,7 @@ SHC_3BB0A8C1_0x004D20C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented -SHC_3BB0A8C1_0x004D2660 | 86.1% | ebx/ebp regalloc differs +SHC_3BB0A8C1_0x004D2660 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D2730 | 100.0% | Reimplemented From b225b0fe22ddf4a962e1426b0817f39b9bdb71ec Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:22:29 +0200 Subject: [PATCH 090/104] reimplement: SHC_3BB0A8C1_0x004CD8F0 100.00% Reimplemented --- .../AICState/giveMoveCommandToSortieUnits.cpp | 52 +++++++++---------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp index 30db96be3..5423d3339 100644 --- a/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp @@ -35,36 +35,36 @@ namespace AI { MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::syncBehaviourType7UnitTribes, this)(playerID); int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + bool sendToKeep = true; if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitMeleeMin) { - DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; - return; - } - - int unitID = DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitID; - if (unitID != 0 - && DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitUID - == DAT_UnitsState::instance.units[unitID].uid - && MACRO_CALL_MEMBER( - OpenSHC::Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, - DAT_PathFindingState::ptr)(playerID, - (short)DAT_TileMapState::instance - .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], - (short) - DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], - 0) - != 0) { - MACRO_CALL_MEMBER( - OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, - DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[unitID].x, - DAT_UnitsState::instance.units[unitID].y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); - DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; - return; + sendToKeep = false; + } else { + int unitID = DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitID; + if (unitID != 0 + && DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitUID + == DAT_UnitsState::instance.units[unitID].uid + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + 0) + != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, + DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[unitID].x, + DAT_UnitsState::instance.units[unitID].y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + sendToKeep = false; + } } } - - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + if (sendToKeep) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index a489fce92..244e70adf 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35281,7 +35281,7 @@ SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented SHC_3BB0A8C1_0x004CD7B0 | 76.1% | Register allocation differs -SHC_3BB0A8C1_0x004CD8F0 | 74.2% | Register allocation differs +SHC_3BB0A8C1_0x004CD8F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented From 1b90c074058d5a7c3969e5cff4dd607778ff25d2 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:22:30 +0200 Subject: [PATCH 091/104] reimplement: SHC_3BB0A8C1_0x004CD7B0 100.00% Reimplemented --- .../AI/AICState/instructTribe166ToMove.cpp | 33 +++++++------------ status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp index 22d967f1f..f7d69ad60 100644 --- a/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp +++ b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp @@ -41,28 +41,19 @@ namespace AI { if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && keepEnclosed == FALSE) { MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); - DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; - return; + } else if (DAT_TribesState::instance.tribes[tribeID].size >= this->aics[aicIndex].SortieUnitRangedMin) { + if (DAT_GameState::instance.playerDataArray[playerID].someXPosition <= 0 + || DAT_GameState::instance.playerDataArray[playerID].someYPosition <= 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_GameState::instance.playerDataArray[playerID].someXPosition, + DAT_GameState::instance.playerDataArray[playerID].someYPosition, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } } - - if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitRangedMin) { - DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; - return; - } - - if (DAT_GameState::instance.playerDataArray[playerID].someXPosition <= 0 - || DAT_GameState::instance.playerDataArray[playerID].someYPosition <= 0) { - MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); - DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; - return; - } - - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)( - tribeID); - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( - tribeID, DAT_GameState::instance.playerDataArray[playerID].someXPosition, - DAT_GameState::instance.playerDataArray[playerID].someYPosition, 0, 0, - OpenSHC::Map::Units::Instructions::UMSE_0); DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 244e70adf..08f996d47 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35279,7 +35279,7 @@ SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented -SHC_3BB0A8C1_0x004CD7B0 | 76.1% | Register allocation differs +SHC_3BB0A8C1_0x004CD7B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CD8F0 | 100.0% | Reimplemented From f7e70f0d70f28c9ddb40d56484e1eb24552c7d53 Mon Sep 17 00:00:00 2001 From: Gynt Date: Mon, 5 Oct 2026 23:30:18 +0200 Subject: [PATCH 092/104] reimplement: SHC_3BB0A8C1_0x004CC420 100.00% Reimplemented --- .../generateSiegeCreationInformation.cpp | 29 ++++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp index 8bdf66eef..dbc9260e1 100644 --- a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp +++ b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp @@ -17,6 +17,7 @@ namespace AI { { if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) return; + int i = 0; if (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SOLITARY) return; @@ -30,22 +31,24 @@ namespace AI { int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; - int i; - for (i = 0; i < locationCount; i++) { - if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][i] > 0 + while (i < locationCount) { + int index = i; + i++; + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][index] > 0 && buildingID == DAT_GameState::instance.playerDataArray[playerID] - .aiSiegeCreationInformation[siegeIndex][i] - .buildingID) - break; + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].unitID + = unitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].uid + = DAT_UnitsState::instance.units[unitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID = 0; + return; + } } - if (i >= locationCount) - return; - - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID = unitID; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid - = DAT_UnitsState::instance.units[unitID].uid; - DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].buildingID = 0; } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 08f996d47..b6937b669 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34867,7 +34867,7 @@ SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC390 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC420 | 39.4% | regalloc/locals differ +SHC_3BB0A8C1_0x004CC420 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented From f66c8f2bb1b6d5c3bc8c667212adf7a189e06a07 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 03:58:32 +0200 Subject: [PATCH 093/104] reimplement: SHC_3BB0A8C1_0x004D2A70 54.27% regalloc/spills differ --- src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp | 13 ++++++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp index 2f1047fba..113ea823f 100644 --- a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -21,6 +21,7 @@ namespace AI { int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == 0) return; + int i = 0; int aicIndex = aiType - 1; DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter++; @@ -28,12 +29,13 @@ namespace AI { >= this->aics[aicIndex].RaidRetargetDelay) DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter = 0; - if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk == 0) + int totalRaidingTroopsUnk = DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk; + if (totalRaidingTroopsUnk == 0) DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown > 0) DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown--; - for (int i = 0; i < 6; i++) { + for (; i < 6; i++) { int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[180 + i]; DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; if (tribeID == 0) @@ -43,20 +45,21 @@ namespace AI { continue; int buildingID = 0; + int playerID_askerUnk = DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk; if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( - DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + playerID_askerUnk) != 0) { if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) continue; buildingID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, - this)(DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk, i); + this)(playerID_askerUnk, i); if (buildingID == 0) buildingID = MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); } else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown == 0 - && DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk < MACRO_CALL_MEMBER( + && totalRaidingTroopsUnk < MACRO_CALL_MEMBER( OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) { if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) continue; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index b6937b669..d9f44b24c 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35501,7 +35501,7 @@ SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D28B0 | 86.3% | select call block layout diffe -SHC_3BB0A8C1_0x004D2A70 | 30.0% | Register allocation differs +SHC_3BB0A8C1_0x004D2A70 | 54.3% | regalloc/spills differ SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented From 595344fc7c118309c24ec2a9b93dc8a88e1423db Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 03:58:33 +0200 Subject: [PATCH 094/104] reimplement: SHC_3BB0A8C1_0x004CDCF0 76.14% regalloc differs --- .../AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp | 3 ++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp index 1004fb6c6..f03897a4f 100644 --- a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -16,6 +16,7 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CDCF0 BOOLEnum AICState::aiShouldAttackOrWaitForTeamCoordination(int playerID) { + int otherPlayerID = 1; int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == OpenSHC::AI::AIT_NULL) return FALSE; @@ -38,7 +39,7 @@ namespace AI { int readyTeamMembers = 0; int unreadyTeamMembers = 0; - for (int otherPlayerID = 1; otherPlayerID < 9; otherPlayerID++) { + for (; otherPlayerID < 9; otherPlayerID++) { if (otherPlayerID == playerID) continue; if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index d9f44b24c..3feb5ddf1 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35293,7 +35293,7 @@ SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 41.8% | Register allocation differs +SHC_3BB0A8C1_0x004CDCF0 | 76.1% | regalloc differs SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented From 11e62c4903bbf4a816a81c68cf4f76e0373cd056 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 03:58:33 +0200 Subject: [PATCH 095/104] reimplement: SHC_3BB0A8C1_0x004D1320 72.34% Style pass, asm differs --- src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp | 3 ++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp index a54ab0211..7e3fe3be9 100644 --- a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp +++ b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp @@ -31,11 +31,12 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D1320 void AICState::renderDebugDataAiInfo(int x, int y, int width, int height) { + int playerID = 1; char text[1000]; // One row of 24 pixels per player, starting one row below y y += 24; - for (int playerID = 1; playerID <= 8; playerID++, y += 24) { + for (; playerID <= 8; playerID++, y += 24) { if (DAT_GameState::instance.playerDataArray[playerID].playerDeathRelated != 0) { continue; } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 3feb5ddf1..dad7f08df 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35463,7 +35463,7 @@ SHC_3BB0A8C1_0x004D11C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D12A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1320 | 72.3% | regalloc differs +SHC_3BB0A8C1_0x004D1320 | 72.3% | Style pass, asm differs SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending From fb6fbbca258d5ab1f38d12313432755c37054018 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 03:58:34 +0200 Subject: [PATCH 096/104] reimplement: SHC_3BB0A8C1_0x004CCFB0 100.00% Reimplemented --- .../checksAndGenerateAITribesForPlayerIfNotExisting.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp index d44cd5889..00347b846 100644 --- a/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp +++ b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp @@ -15,8 +15,9 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CCFB0 int AICState::checksAndGenerateAITribesForPlayerIfNotExisting(int playerID, int maxAmount, BOOLEnum checkOnly) { + int offset = 0; int baseOffset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[1]; - for (int offset = 0; offset < maxAmount; offset++) { + for (; offset < maxAmount; offset++) { if (offset >= 10) { return 0; } From e8717d95815bfd1a0c6852e9df0734d4ea1c109f Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 06:31:53 +0200 Subject: [PATCH 097/104] reimplement: SHC_3BB0A8C1_0x004CFCD0 100.00% Reimplemented --- src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp | 2 +- .../AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp | 2 +- status/addresses-SHC-3BB0A8C1.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp index 3a7bf0c41..a7da31806 100644 --- a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -81,8 +81,8 @@ namespace AI { int targetTribeID = shieldTargetTribeIDs[shieldCount]; DAT_TribesState::instance.tribes[tribeID].field56_0x1f2 = targetTribeID; DAT_TribesState::instance.tribes[tribeID].uid2 = DAT_TribesState::instance.tribes[targetTribeID].uid; - int targetUnitID = DAT_TribesState::instance.tribes[targetTribeID].selectionTargetUnitID; DAT_TribesState::instance.tribes[targetTribeID].tribeID = tribeID; + int targetUnitID = DAT_TribesState::instance.tribes[targetTribeID].selectionTargetUnitID; int tile = DAT_UnitsState::instance.units[targetUnitID].tile; shieldCount++; if (tile <= 0) diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp index f03897a4f..5f0124873 100644 --- a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -20,9 +20,9 @@ namespace AI { int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; if (aiType == OpenSHC::AI::AIT_NULL) return FALSE; - int aicIndex = aiType - 1; if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) return TRUE; + int aicIndex = aiType - 1; if (DAT_GameState::instance.playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] .totalTroopValue diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index dad7f08df..4433551a2 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35387,7 +35387,7 @@ SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 99.4% | reload order after call differ +SHC_3BB0A8C1_0x004CFCD0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented From 941e9753a9648b8839de43acbc4a426bf6e4ce8c Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 06:31:53 +0200 Subject: [PATCH 098/104] reimplement: SHC_3BB0A8C1_0x004CDCF0 77.16% regalloc/counters differ --- status/addresses-SHC-3BB0A8C1.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 4433551a2..26eab9956 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35293,7 +35293,7 @@ SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 76.1% | regalloc differs +SHC_3BB0A8C1_0x004CDCF0 | 77.2% | regalloc/counters differ SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented From 4151866d4964d1de31413f1575b148518141f0a1 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 21:39:30 +0200 Subject: [PATCH 099/104] reimplement: SHC_3BB0A8C1_0x004D2000 40.62% regalloc/frame differ --- src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp | 10 +++++----- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index d82382723..08a7d3eca 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -32,9 +32,9 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004D2000 void AICState::aiCreateSiegeUnits(int playerID) { - int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; - if (aiType == OpenSHC::AI::AIT_NULL) + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) return; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; if (guildID <= 0) return; @@ -46,7 +46,7 @@ namespace AI { for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; - if (locationCount >= 4) + if (locationCount > 3) locationCount = 3; for (int i = 0; i < locationCount; i++) { @@ -141,13 +141,13 @@ namespace AI { .requiredGold; DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] - + this->aics[aiType - 1].defSiegeEngineGoldThreshold + + this->aics[aicIndex].defSiegeEngineGoldThreshold < requiredGold) continue; if (DAT_GameState::instance.playerDataArray[playerID] .aiSiegeCreationInformation[siegeIndex][i] .delay - < this->aics[aiType - 1].defSiegeEngineBuildDelay) + < this->aics[aicIndex].defSiegeEngineBuildDelay) continue; int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[location]; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 26eab9956..7fc598f2e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 39.4% | frame size and regalloc differ +SHC_3BB0A8C1_0x004D2000 | 40.6% | regalloc/frame differ SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From ad0fdef93a2eaddcd8bd25a8dad13a63191e2eaa Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 21:39:30 +0200 Subject: [PATCH 100/104] reimplement: SHC_3BB0A8C1_0x004CBA50 90.73% flag slot order differs --- ...oBuyWhenLowOnResourceAndSnoozeBuildings.cpp | 18 +++++++++--------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp index 4db7857fd..ccf876a64 100644 --- a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp +++ b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp @@ -22,19 +22,19 @@ namespace AI { return; } - int allowFletchers = 1; int allowIronMines = 1; - int allowFarms = 1; + int allowArmourers = 1; + int allowFletchers = 1; int allowPitchRigs = 1; int allowWoodcutters = 1; int allowPoleturners = 1; - int allowQuarries = 1; int allowBlacksmiths = 1; - int allowArmourers = 1; int allowTanners = 1; + int allowQuarries = 1; int allowBakeries = 1; int allowBreweries = 1; int allowInns = 1; + int allowFarms = 1; int allowWells = 1; int buildingBurning = MACRO_CALL_MEMBER( @@ -144,8 +144,8 @@ namespace AI { allowFletchers = 1; allowIronMines = 0; allowQuarries = 0; - allowFarms = 0; allowPitchRigs = 0; + allowFarms = 0; allowWoodcutters = 0; allowPoleturners = 1; allowBlacksmiths = 1; @@ -172,17 +172,17 @@ namespace AI { DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = true; } - DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WOODCUTTERSHUT] = !allowWoodcutters; - DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_PITCHRIG] = !allowPitchRigs; - DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_POLETURNER] = !allowPoleturners; - DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_ARMOURER] = !allowArmourers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_TANNER] = !allowTanners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WOODCUTTERSHUT] = !allowWoodcutters; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BAKERY] = !allowBakeries; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BREWERY] = !allowBreweries; DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_INN] = !allowInns; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; if (allowFarms) { DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = false; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 7fc598f2e..5d0d101e3 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34853,7 +34853,7 @@ SHC_3BB0A8C1_0x004CB940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 85.1% | Reimplemented functionally, stack and cached load differencesd +SHC_3BB0A8C1_0x004CBA50 | 90.7% | flag slot order differs SHC_3BB0A8C1_0x004CBE30 | 100.0% | Reimplemented From 2849d3307e5623645fb39f0129b1264bcaf83ee3 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 21:55:28 +0200 Subject: [PATCH 101/104] reimplement: SHC_3BB0A8C1_0x004D3AE0 43.38% regalloc/spills differ --- src/OpenSHC/AI/AICState/aiRecruitUnits.cpp | 4 +++- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp index 4cf7a40a3..2f59cee0d 100644 --- a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -137,7 +137,9 @@ namespace AI { return; int buildingID; - if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER) + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_LADDER) buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) buildingID = DAT_GameState::instance.playerDataArray[playerID].tunnelersGuild.id; diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 5d0d101e3..ec85b9f1b 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35527,7 +35527,7 @@ SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 41.6% | regalloc differs +SHC_3BB0A8C1_0x004D3AE0 | 43.4% | regalloc/spills differ SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented From d04e5b1af93bdbb75bdcf6bec8a53f87321cfcec Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 21:56:56 +0200 Subject: [PATCH 102/104] cheat sheet: what Ghidra's decompilation is and is not reliable for Copying the original's loop and block structure back into the source made four AI::AICState functions worse; the literals, where a value is computed, and arms with identical bodies are the parts that transfer. Co-Authored-By: Claude Opus 5 --- IMPLEMENTATION_CHEAT_SHEET.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index 92633bede..bdb40ab25 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -429,6 +429,32 @@ bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it exit itself and places it where the original does (`aiRequiresExtraOxtethers`). Likewise put the block the original keeps inline first: `if (best > 20) { store; return 1; } return 0;` rather than the inverted guard. +### Reading Ghidra against the assembly + +Ghidra's decompilation shows the original's *variable and loop structure*, but reproducing that structure +literally usually makes the match **worse**, because MSVC derives the same shapes from simpler source. Measured on +`AI::AICState` (2026-10-06): writing the inner loop of `aiCreateSiegeUnits` as the `do { i = slot; slot++; ... } +while (slot < count)` that Ghidra shows dropped it from 55% to 33%; giving the engineer-tribe block the single +physical copy the original has (`LAB_004d22d3`, two predecessors) cost 20 points; restoring a `unitType` local that +the original keeps in a stack slot cost 1; and the countdown loop Ghidra shows in `computeEnemyKeepApproachTile` +(`do { ... } while (--n != 0)`) cost 3. Strength reduction, induction variables and block duplication are the +compiler's doing, so do not copy them back into the source. + +What Ghidra *is* reliable for: + +- **Comparison literals.** `if (count >= 4) count = 3;` against the original's `cmp edi, 3; jle` says the source + wrote `> 3`. Worth 0.6% on its own and it is free (`aiCreateSiegeUnits`). +- **Where a value is computed.** `_aicOffset = (_aiType + ~AIT_NULL) * 0x2a4` appearing once at the top of the + decompilation, against our `aics[aiType - 1]` at each use, says the source had `int aicIndex = pd.aiType - 1;` + as its own statement (+1%). +- **Arms with identical bodies.** Ghidra renders `if (t == UT_E_ENGINEER) { b = guild; } else if (t == UT_E_LADDER) + { b = guild; }` where we had written `t == ENGINEER || t == LADDER`. Splitting the `||` into two arms with the + same body took `aiRecruitUnits` from 41.6% to 43.4%. This is the duplicate-the-shared-block rule again, and it is + the one structural thing that transfers. +- **Field types and flat array access.** `*(int *)((int)&DAT_AICState + (index + aicIndex * 0xa9) * 4 + 0x184)` is + `(&aics[aicIndex].DefUnit1)[index]` - the AIC block indexed as a flat `int` array, 169 ints per entry. The + per-field struct access and this flat form appear in the same function and both are real. + ### GOTO A function may contain multiple GOTOs. From f75c623eef5cd3f5c7ff1499ee989f00d7202ec5 Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 23:24:33 +0200 Subject: [PATCH 103/104] reimplement: SHC_3BB0A8C1_0x004D2000 41.87% regalloc/frame differ --- .../AI/AICState/aiCreateSiegeUnits.cpp | 74 ++++++------------- status/addresses-SHC-3BB0A8C1.txt | 2 +- 2 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp index 08a7d3eca..403a5138e 100644 --- a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -91,32 +91,7 @@ namespace AI { continue; } - if (unitID == 0) { - if (buildingID != 0) { - int requiredEngineers = MACRO_CALL_MEMBER( - OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, - DAT_BuildingsState::ptr)(buildingID); - if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount - == requiredEngineers) - continue; - if (requiredEngineers - > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { - DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; - continue; - } - int tribeID - = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, - this)(playerID, requiredEngineers); - DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; - DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType - = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, - DAT_TribesState::ptr)(tribeID, - OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, - DAT_BuildingsState::instance.buildings[buildingID].uid, 0); - return; - } - + if (unitID == 0 && buildingID == 0) { if (((short)DAT_TileMapState::instance.BuildingLayer[location]) == 0) { if (siegeIndex < 2) continue; @@ -200,32 +175,7 @@ namespace AI { = 0; return; } - - if (unitID != 0) { - if (buildingID != 0) { - int requiredEngineers = MACRO_CALL_MEMBER( - OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, - DAT_BuildingsState::ptr)(buildingID); - if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount - == requiredEngineers) - continue; - if (requiredEngineers - > DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated) { - DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; - continue; - } - int tribeID - = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, - this)(playerID, requiredEngineers); - DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; - DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType - = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; - MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, - DAT_TribesState::ptr)(tribeID, - OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, - DAT_BuildingsState::instance.buildings[buildingID].uid, 0); - return; - } + if (unitID != 0 && buildingID == 0) { int remainingEngineers = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, DAT_UnitsState::ptr)(unitID); @@ -257,8 +207,28 @@ namespace AI { DAT_UnitsState::instance.units[unitID].uid, 0); return; } + + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount == requiredEngineers) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated >= requiredEngineers) { + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; } } } + } } diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index ec85b9f1b..19c7e8b9e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -35479,7 +35479,7 @@ SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 40.6% | regalloc/frame differ +SHC_3BB0A8C1_0x004D2000 | 41.9% | regalloc/frame differ SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending From f99565d79620e551020cac31ec86ee77aa1ef92c Mon Sep 17 00:00:00 2001 From: Gynt Date: Tue, 6 Oct 2026 23:26:54 +0200 Subject: [PATCH 104/104] cheat sheet: correct the Ghidra note - measure restructurings with reccmp The same change (one physical copy of a block the original shares) was worth +1.3 points on aiCreateSiegeUnits and -10 on renderDebugDataAiInfo, and quick_diff's ratio called the first one wrong. Also record the call count from push_pattern.py as the signal that found it. Co-Authored-By: Claude Opus 5 --- IMPLEMENTATION_CHEAT_SHEET.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index bdb40ab25..53c796467 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -431,14 +431,20 @@ bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it ### Reading Ghidra against the assembly -Ghidra's decompilation shows the original's *variable and loop structure*, but reproducing that structure -literally usually makes the match **worse**, because MSVC derives the same shapes from simpler source. Measured on -`AI::AICState` (2026-10-06): writing the inner loop of `aiCreateSiegeUnits` as the `do { i = slot; slot++; ... } -while (slot < count)` that Ghidra shows dropped it from 55% to 33%; giving the engineer-tribe block the single -physical copy the original has (`LAB_004d22d3`, two predecessors) cost 20 points; restoring a `unitType` local that -the original keeps in a stack slot cost 1; and the countdown loop Ghidra shows in `computeEnemyKeepApproachTile` -(`do { ... } while (--n != 0)`) cost 3. Strength reduction, induction variables and block duplication are the -compiler's doing, so do not copy them back into the source. +Ghidra's decompilation shows the original's *variable and loop structure*, and whether copying a given piece of it +back into the source helps is **not predictable - measure every one with reccmp, never with `quick_diff`'s ratio.** +Measured on `AI::AICState` (2026-10-06), the same change went both ways: + +- Giving the engineer-tribe block of `aiCreateSiegeUnits` the single physical copy the original has + (`LAB_004d22d3`, two predecessors) was **+1.3 points** by reccmp - while `quick_diff` scored it 2 points *worse*. +- Doing the same to the three duplicated render/sprintf calls of `renderDebugDataAiInfo`, which Ghidra shows as one + call with a selected string and colour, cost **10 points** (72.3% -> 62.0%). + +`quick_diff`'s difflib ratio cannot judge a restructuring: it rewards alignment, and a different-but-better block +layout shows up as a worse ratio. Use it for local edits, and build + reccmp for anything that moves a block. +Several other shapes Ghidra showed were measured worse and are not worth retrying: the +`do { i = slot; slot++; ... } while` inner loop in `aiCreateSiegeUnits`, a `unitType` stack local there, and the +countdown loop in `computeEnemyKeepApproachTile`. What Ghidra *is* reliable for: @@ -449,8 +455,12 @@ What Ghidra *is* reliable for: as its own statement (+1%). - **Arms with identical bodies.** Ghidra renders `if (t == UT_E_ENGINEER) { b = guild; } else if (t == UT_E_LADDER) { b = guild; }` where we had written `t == ENGINEER || t == LADDER`. Splitting the `||` into two arms with the - same body took `aiRecruitUnits` from 41.6% to 43.4%. This is the duplicate-the-shared-block rule again, and it is - the one structural thing that transfers. + same body took `aiRecruitUnits` from 41.6% to 43.4%. +- **The number of calls.** `push_pattern.py` counts the calls whose argument set-up it can see on each side; more on + our side than the original's means we have duplicated a block the original keeps once. That count, not the difflib + ratio, is what identified the `aiCreateSiegeUnits` engineer block correctly. Its per-call `pushes/interleaved` + columns answer the other half: a contiguous push run means the arguments were already in locals, pushes + interleaved with loads mean the expression was written inline at the call. - **Field types and flat array access.** `*(int *)((int)&DAT_AICState + (index + aicIndex * 0xa9) * 4 + 0x184)` is `(&aics[aicIndex].DefUnit1)[index]` - the AIC block indexed as a flat `int` array, 169 ints per entry. The per-field struct access and this flat form appear in the same function and both are real.