// SPDX-License-Identifier: MIT // Copyright © 2007-2025 Sander Stolk #include "ai.h" #include "game.h" #include "info.h" #include "factions.h" #include "units.h" #include "structures.h" #include "music.h" #include "fileio.h" #define MAX_DIFFERENT_TEAM_SCRIPTED_AI 50 struct TeamScriptedAI { enum UnitGroupAI groupAI; enum UnitType unitType; int minAmount; int maxAmount; int staging_tile; }; struct TeamCurrentAI { int currentTeam; int amount; }; struct TeamAI { int amountOfTeamScriptedAI; struct TeamScriptedAI teamScriptedAI[MAX_DIFFERENT_TEAM_SCRIPTED_AI]; struct TeamCurrentAI teamCurrentAI; // duration between teams and countdown int pauseBetweenTeams; int currentPause; // maximum duration staging (for meeting up at a rally point, before moving on to attack) and countdown int maximumStagingDuration; int currentStagingDuration; }; static struct TeamAI teamAI[MAX_DIFFERENT_FACTIONS - 1]; static struct PriorityStructureAI priorityStructureAI; void doTeamAILogic() { int i, j, k; int possibleUnitToBuild[ENEMY_UNITS_QUEUE]; int possibleUnitToBuildBy[ENEMY_UNITS_QUEUE]; int amountOfPossibleUnitsToBuild; enum UnitType unitTypesToBuild; int amountOfUnitsOnMap; int formingBecomesStaging; startProfilingFunction("doTeamAILogic"); for (i=0; i 0) ? teamAI[i].maximumStagingDuration : max(teamAI[i].currentStagingDuration, (abs(X_FROM_TILE(unit[j].logic_aid) - unit[j].x) + abs(Y_FROM_TILE(unit[j].logic_aid) - unit[j].y)) * unitInfo[unit[j].info].speed); } } } } if (teamAI[i].currentStagingDuration > 0) teamAI[i].currentStagingDuration--; if (teamAI[i].currentPause > 0) { teamAI[i].currentPause--; continue; } // set new team to create teamAI[i].teamCurrentAI.currentTeam = (teamAI[i].teamCurrentAI.currentTeam + 1) % teamAI[i].amountOfTeamScriptedAI; if (teamAI[i].teamScriptedAI[teamAI[i].teamCurrentAI.currentTeam].minAmount <= getUnitLimit(i+1)-amountOfUnitsOnMap) { // determine the number of units for the team and create it teamAI[i].teamCurrentAI.amount = MIN( getUnitLimit(i+1)-amountOfUnitsOnMap, // max additional units allowed for this side teamAI[i].teamScriptedAI[teamAI[i].teamCurrentAI.currentTeam].minAmount + rand() % (teamAI[i].teamScriptedAI[teamAI[i].teamCurrentAI.currentTeam].maxAmount - teamAI[i].teamScriptedAI[teamAI[i].teamCurrentAI.currentTeam].minAmount)); // ideal additional units for this side unitTypesToBuild = teamAI[i].teamScriptedAI[teamAI[i].teamCurrentAI.currentTeam].unitType; amountOfPossibleUnitsToBuild = 0; for (j=0; j= 0) { possibleUnitToBuild[amountOfPossibleUnitsToBuild] = j; possibleUnitToBuildBy[amountOfPossibleUnitsToBuild] = k; amountOfPossibleUnitsToBuild++; if (amountOfPossibleUnitsToBuild == ENEMY_UNITS_QUEUE) break; } } } if (amountOfPossibleUnitsToBuild > 0) { for (j=0; j= MAX_DIFFERENT_FACTIONS-1) continue; // ignore this line; it mentions an enemy that does not occur in the current scenario if (teamAI[i].amountOfTeamScriptedAI == MAX_DIFFERENT_TEAM_SCRIPTED_AI) error("Scenario specified more AI Teams for the following faction than allowed:", factionInfo[getFaction(i+1)].name); if (!strncmp(positionInInput, "InitialPause", strlen("InitialPause"))) { sscanf(positionInInput + strlen("InitialPause"), ",%i", &teamAI[i].currentPause); teamAI[i].currentPause = (2*(teamAI[i].currentPause * FPS)) / gameSpeed; continue; } if (!strncmp(positionInInput, "Pause", strlen("Pause"))) { sscanf(positionInInput + strlen("Pause"), ",%i", &teamAI[i].pauseBetweenTeams); teamAI[i].pauseBetweenTeams = (2*(teamAI[i].pauseBetweenTeams * FPS)) / gameSpeed; continue; } if (!strncmp(positionInInput, "MaxStagingDuration", strlen("MaxStagingDuration"))) { sscanf(positionInInput + strlen("MaxStagingDuration"), ",%i", &teamAI[i].maximumStagingDuration); teamAI[i].maximumStagingDuration = (2*(teamAI[i].maximumStagingDuration * FPS)) / gameSpeed; continue; } if (!strncmp(positionInInput, "Kamikaze", strlen("Kamikaze"))) { teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].groupAI = UGAI_KAMIKAZE; positionInInput += strlen("Kamikaze") + 1; /* } else if (!strncmp(positionInInput, "Flee", strlen("Flee"))) { teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].groupAI = UGAI_FLEE; positionInInput += strlen("Flee,"); } else if (!strncmp(positionInInput, "Staging", strlen("Staging"))) { teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].groupAI = UGAI_STAGING; positionInInput += strlen("Staging,");*/ } else if (!strncmp(positionInInput, "Hunt", strlen("Hunt"))) { teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].groupAI = UGAI_HUNT; positionInInput += strlen("Hunt") + 1; } else if (!strncmp(positionInInput, "Normal", strlen("Normal"))) { teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].groupAI = UGAI_HUNT; positionInInput += strlen("Normal") + 1; } else error("Invalid team AI in scenario", oneline); unitType = 0; for (;;) { if (!strncmp(positionInInput, "Foot", strlen("Foot"))) { unitType |= BIT(UT_FOOT); positionInInput += strlen("Foot,"); } else if (!strncmp(positionInInput, "Wheeled", strlen("Wheeled"))) { unitType |= BIT(UT_WHEELED); positionInInput += strlen("Wheeled,"); } else if (!strncmp(positionInInput, "Tracked", strlen("Tracked"))) { unitType |= BIT(UT_TRACKED); positionInInput += strlen("Tracked,"); } else if (!strncmp(positionInInput, "Aerial", strlen("Aerial"))) { unitType |= BIT(UT_AERIAL); positionInInput += strlen("Aerial,"); } else break; } if (unitType == 0) // if no unitType was specified, all unitTypes are OK to use unitType = BIT(UT_FOOT) | BIT(UT_WHEELED) | BIT(UT_TRACKED) | BIT(UT_AERIAL); teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].unitType = unitType; sscanf(positionInInput, "%i,%i", &teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].minAmount, &teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].maxAmount); while (*positionInInput != 0 && *positionInInput != 'R') positionInInput++; if (sscanf(positionInInput, "Rally:%i,%i", &j, &k) == 2) teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].staging_tile = TILE_FROM_XY(j,k); else teamAI[i].teamScriptedAI[teamAI[i].amountOfTeamScriptedAI].staging_tile = -1; teamAI[i].amountOfTeamScriptedAI++; } closeFile(fp); } struct RebuildItem { int enabled; int nr; }; struct RebuildQueue { struct RebuildItem structure[ENEMY_STRUCTURES_QUEUE]; struct RebuildItem unit[ENEMY_UNITS_QUEUE]; int structure_delay; int unit_delay; }; struct RebuildQueue rebuildQueue[MAX_DIFFERENT_FACTIONS]; void doRebuildAILogic() { int freeQueueSlots[MAX_DIFFERENT_FACTIONS]; int foundationQueueSlot[MAX_DIFFERENT_FACTIONS]; enum Side curSide; int okToPlaceStructure; struct EnvironmentLayout *envLayout; int foundationInfo = 0; int structureNr; int i, j, k; startProfilingFunction("doRebuildAILogic"); // STRUCTURES TIME startProfilingFunction("doRebuildAILogic (structures)"); // get foundation info for (i=0; i= 0 && getStructuresQueue(curSide)[foundationQueueSlot[curSide]].status == DONE) { // lay down foundation tile if possible okToPlaceStructure = 1; for (j=0; jcontains_structure == curSide) { // found foundation tile here, but is a unit obscuring it for placement? if (envLayout->contains_unit != -1) okToPlaceStructure = 0; } else { // perhaps we can place the foundation tile here then if (rebuildQueue[curSide].structure_delay == 0 && foundationQueueSlot[curSide] >= 0 && addStructure(curSide, structure[structureNr].x + k, structure[structureNr].y + j, getStructuresQueue(curSide)[foundationQueueSlot[curSide]].itemInfo, 0) >= 0) { removeItemStructuresQueueNr(curSide, foundationQueueSlot[curSide], 0, 0); foundationQueueSlot[curSide] = -1; rebuildQueue[curSide].structure_delay = REBUILD_DELAY_CONCRETE; }// else // should wait with placing this structure anyway, even if it now can be placed okToPlaceStructure = 0; } } } // place structure if possible if (okToPlaceStructure) { if (rebuildQueue[curSide].structure_delay == 0 && getStructuresQueue(curSide)[i].status == DONE) { j = addStructure(curSide, structure[structureNr].x, structure[structureNr].y, structure[structureNr].info, 0); if (j >= 0) { structure[structureNr].group = 0; // taken care of removeItemStructuresQueueNr(curSide, i, 0, 0); rebuildQueue[curSide].structure_delay = getRebuildDelayStructures(curSide); for (k=MAX_DIFFERENT_FACTIONS; k= 0) { addItemStructuresQueue(i, foundationInfo, k); foundationQueueSlot[i] = 0; // set it to 0 temporarily, to be able to determine if one is being built } } } // get amount of free queue slots per side for (i=1; i= 0) { if (addItemStructuresQueue(structure[i].side, structure[i].info, j)) { structure[i].group |= QUEUED_FOR_REPLACEMENT; rebuildQueue[structure[i].side].structure[ENEMY_STRUCTURES_QUEUE - freeQueueSlots[structure[i].side]].enabled = 1; rebuildQueue[structure[i].side].structure[ENEMY_STRUCTURES_QUEUE - freeQueueSlots[structure[i].side]].nr = i; freeQueueSlots[structure[i].side]--; } } else structure[i].group |= TECHTREE_INSUFFICIENT; } } } } stopProfilingFunction(); // of structures // UNITS TIME startProfilingFunction("doRebuildAILogic (units)"); for (i=1; i= 0) { if (addItemUnitsQueue(curSide, unit[i].info, j)) { freeQueueSlots[curSide]--; unit[i].group |= QUEUED_FOR_REPLACEMENT; for (k=0; k 0) rebuildQueue[i].structure_delay--; if (rebuildQueue[i].unit_delay > 0) rebuildQueue[i].unit_delay--; } stopProfilingFunction(); // of units stopProfilingFunction(); // of entire function } void adjustAIUnitAdded(enum Side side, int itemNr, int unitNr) { struct Unit *srcUnit; struct Unit *dstUnit = unit + unitNr; if (rebuildQueue[side].unit[itemNr].enabled) { srcUnit = unit + rebuildQueue[side].unit[itemNr].nr; dstUnit->group = srcUnit->group & ~(AWAITING_REPLACEMENT | TECHTREE_INSUFFICIENT | QUEUED_FOR_REPLACEMENT); dstUnit->guard_tile = srcUnit->guard_tile; dstUnit->retreat_tile = srcUnit->retreat_tile; dstUnit->logic = UL_GUARD_RETREAT; srcUnit->group = 0; // taken care of return; // no need to set enabled to 0, because that'll be done right after this anyway by removeAIRebuildUnitNr(); } dstUnit->group = UGAI_FORMING; } void removeAIRebuildUnitNr(enum Side side, int nr) { struct RebuildItem *ri = &rebuildQueue[side].unit[nr]; int i; if (ri->enabled) unit[ri->nr].group &= (~QUEUED_FOR_REPLACEMENT); for (i=nr; imax_size) return(0); memcpy (dest,&teamAI,size); return (size); } struct RebuildQueue *getRebuildQueue() { return rebuildQueue; } int getRebuildQueueSaveSize(void) { return sizeof(rebuildQueue); } int getRebuildQueueSaveData(void *dest, int max_size) { int size=sizeof(rebuildQueue); if (size>max_size) return(0); memcpy (dest,&rebuildQueue,size); return (size); }