diff --git a/src/structure.cpp b/src/structure.cpp index d5a297b83f3..718a61d986c 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -2703,22 +2703,92 @@ bool IsPlayerDroidLimitReached(int player) return numDroids >= getMaxDroids(player); } +/* + * Look through a ProductionRun and check if any entry is NOT a construction droid + */ +static bool containsNonConstructionDroid(STRUCTURE *psStructure) +{ + CHECK_STRUCTURE(psStructure); + + FACTORY *psFactory = &psStructure->pFunctionality->factory; + + if (psFactory->psAssemblyPoint->factoryInc >= asProductionRun[psFactory->psAssemblyPoint->factoryType].size()) + { + return false; // Don't even have a production list. + } + + ProductionRun &productionRun = asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc]; + + for (ProductionRunEntry &entry : productionRun) + { + DROID_TYPE droidType = entry.psTemplate->droidType; + + if (droidType != DROID_CONSTRUCT && droidType != DROID_CYBORG_CONSTRUCT) + { + return true; + } + } + return false; +} + +/* + * Look ahead and view the next production run entry, if any. + * + * If parameter considerLoops is true, then the function will get the next ProductionRunEntry of the subsequent loop + */ +static ProductionRunEntry *nextProductionRunEntry(STRUCTURE *psStructure, DROID_TEMPLATE *psCurrTemplate, bool considerLoops) +{ + CHECK_STRUCTURE(psStructure); + + FACTORY *psFactory = &psStructure->pFunctionality->factory; + + if (psFactory->psAssemblyPoint->factoryInc >= asProductionRun[psFactory->psAssemblyPoint->factoryType].size()) + { + return nullptr; // Don't even have a production list. + } + + if (psCurrTemplate != nullptr) + { + // Find the entry in the array for the current template + ProductionRun &productionRun = asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc]; + ProductionRun::iterator currEntry = std::find(productionRun.begin(), productionRun.end(), psCurrTemplate); + + if (currEntry != productionRun.end()) + { + // Look ahead + ProductionRun::iterator nextEntry = std::next(currEntry); + + if (nextEntry != productionRun.end()) + { + return &(*nextEntry); + } + else if (considerLoops && psFactory->productionLoops > 0) + { + // We have reached the end of the ProductionRun + // Loop back to the start. Retrieve the template from the beginning + return &(*productionRun.begin()); + } + } + } + return nullptr; +} + // Return codes for the checkHaltOnMaxUnitsReached function -enum ReasonForProductionHalt : uint8_t { - NO_HALT = 0, // no limit reached and no halt - DROID_LIMIT = 1, // unit limit reached - MISSING_RELAY = 2, // can't build commander without command relay center - COMMANDER_LIMIT = 3, // commander unit limit reached - CONSTRUCTION_LIMIT = 4 // construction unit limit reached +enum ProductionHaltReason { + NO_HALT, // no limit reached and no halt + DROID_LIMIT, // unit limit reached + MISSING_RELAY, // can't build commander without command relay center + COMMANDER_LIMIT, // commander unit limit reached + CONSTRUCTION_LIMIT // construction unit limit reached }; // Check for max number of units reached and halt production. -static ReasonForProductionHalt checkHaltOnMaxUnitsReached(STRUCTURE *psStructure, bool isMission) +static ProductionHaltReason checkHaltOnMaxUnitsReached(STRUCTURE *psStructure, bool isMission) { CHECK_STRUCTURE(psStructure); char limitMsg[300]; - uint8_t haltReason = NO_HALT; + ProductionHaltReason reason = NO_HALT; int player = psStructure->player; DROID_TEMPLATE *templ = psStructure->pFunctionality->factory.psSubject; @@ -2727,7 +2797,7 @@ static ReasonForProductionHalt checkHaltOnMaxUnitsReached(STRUCTURE *psStructure // then put production on hold & return - we need a message to be displayed here !!!!!!! if (IsPlayerDroidLimitReached(player)) { - haltReason = DROID_LIMIT; + reason = DROID_LIMIT; sstrcpy(limitMsg, _("Can't build any more units, Unit Limit Reached — Production Halted")); } else switch (droidTemplateType(templ)) @@ -2735,12 +2805,12 @@ static ReasonForProductionHalt checkHaltOnMaxUnitsReached(STRUCTURE *psStructure case DROID_COMMAND: if (!structureExists(player, REF_COMMAND_CONTROL, true, isMission)) { - haltReason = MISSING_RELAY; + reason = MISSING_RELAY; ssprintf(limitMsg, _("Can't build \"%s\" without a Command Relay Center — Production Halted"), templ->name.toUtf8().c_str()); } else if (getNumCommandDroids(player) >= getMaxCommanders(player)) { - haltReason = COMMANDER_LIMIT; + reason = COMMANDER_LIMIT; ssprintf(limitMsg, _("Can't build \"%s\", Commander Limit Reached — Production Halted"), templ->name.toUtf8().c_str()); } break; @@ -2748,21 +2818,21 @@ static ReasonForProductionHalt checkHaltOnMaxUnitsReached(STRUCTURE *psStructure case DROID_CYBORG_CONSTRUCT: if (getNumConstructorDroids(player) >= getMaxConstructors(player)) { - haltReason = CONSTRUCTION_LIMIT; - ssprintf(limitMsg, _("Can't build any more \"%s\", Construction Unit Limit Reached — Production Halted"), templ->name.toUtf8().c_str()); + reason = CONSTRUCTION_LIMIT; + ssprintf(limitMsg, _("Can't build any more \"%s\", Construction Unit Limit Reached — Production Halted/Skipped"), templ->name.toUtf8().c_str()); } break; default: break; } - if (haltReason && player == selectedPlayer && (lastMaxUnitMessage == 0 || lastMaxUnitMessage + MAX_UNIT_MESSAGE_PAUSE <= gameTime)) + if (reason && player == selectedPlayer && (lastMaxUnitMessage == 0 || lastMaxUnitMessage + MAX_UNIT_MESSAGE_PAUSE <= gameTime)) { addConsoleMessage(limitMsg, DEFAULT_JUSTIFY, SYSTEM_MESSAGE); lastMaxUnitMessage = gameTime; } - return haltReason; + return reason; } void aiUpdateRepair_handleState(STRUCTURE &station) @@ -2773,7 +2843,7 @@ void aiUpdateRepair_handleState(STRUCTURE &station) // apply logic for current state switch (psRepairFac->state) { - case RepairState::Idle: + case RepairState::Idle: { actionAlignTurret(psStructure, 0); return; @@ -2812,7 +2882,7 @@ RepairEvents aiUpdateRepair_obtainEvents(const STRUCTURE &station, DROID **psDro switch (psRepairFac->state) { case RepairState::Idle: - { + { DROID *psDroid = findSomeoneToRepair(psStructure, (TILE_UNITS * 5 / 2)); *psDroidOut = psDroid; return psDroid? RepairEvents::RepairTargetFound : RepairEvents::NoEvents; @@ -2829,7 +2899,7 @@ RepairEvents aiUpdateRepair_obtainEvents(const STRUCTURE &station, DROID **psDro { return RepairEvents::UnitMovedAway; } - if (psDroid->body >= psDroid->originalBody) + if (psDroid->body >= psDroid->originalBody) { return RepairEvents::UnitReachedMaxHP; } @@ -2861,7 +2931,7 @@ RepairState aiUpdateRepair_handleEvents(STRUCTURE &station, RepairEvents ev, DRO switch (ev) { case RepairEvents::NoEvents: - { + { return psRepairFac->state; }; case RepairEvents::RepairTargetFound: @@ -2910,7 +2980,7 @@ void aiUpdateRepairStation(STRUCTURE &station) ASSERT(nextState != RepairState::Invalid, "Bug! invalid state received."); station.pFunctionality->repairFacility.state = nextState; aiUpdateRepair_handleState(station); - + } static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission) @@ -2920,7 +2990,6 @@ static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission) BASE_OBJECT *psChosenObjs[MAX_WEAPONS] = {nullptr}; BASE_OBJECT *psChosenObj = nullptr; FACTORY *psFactory; - bool bDroidPlaced = false; WEAPON_STATS *psWStats; bool bDirect = false; TARGET_ORIGIN tmpOrigin = ORIGIN_UNKNOWN; @@ -3343,20 +3412,46 @@ static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission) return; } + // CASE 1/3 factory just started production if (psFactory->timeStarted == ACTION_START_TIME) { // also need to check if a command droid's group is full // If the factory commanders group is full - return - if (IsFactoryCommanderGroupFull(psFactory) || checkHaltOnMaxUnitsReached(psStructure, isMission)) + if (IsFactoryCommanderGroupFull(psFactory)) + { + return; + } + + // Check limits (e.g. max trucks, max units, etc.) + ProductionHaltReason reason = checkHaltOnMaxUnitsReached(psStructure, isMission); + if (reason == NO_HALT) + { + //set the time started + psFactory->timeStarted = gameTime; + } + else if (reason == CONSTRUCTION_LIMIT && containsNonConstructionDroid(psStructure)) + { + // if there are other non construction droid entries in the production run + // Skip this entry (which is a construction droid) + ProductionRunEntry *nextEntry = nextProductionRunEntry(psStructure, (DROID_TEMPLATE *)pSubject, true); + if (nextEntry) + { + doNextProduction(psStructure, (DROID_TEMPLATE *)pSubject, ModeImmediate); + } + else + { + return; + } + } + else { return; } - //set the time started - psFactory->timeStarted = gameTime; } + // CASE 2/3 factory is in the middle of production if (psFactory->buildPointsRemaining > 0) { int progress = gameTimeAdjustedAverage(getBuildingProductionPoints(psStructure)); @@ -3372,40 +3467,58 @@ static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission) psFactory->buildPointsRemaining -= progress; } - //check for manufacture to be complete - if (psFactory->buildPointsRemaining <= 0 && !IsFactoryCommanderGroupFull(psFactory) && !checkHaltOnMaxUnitsReached(psStructure, isMission)) + // CASE 3/3 factory has completed production + if (psFactory->buildPointsRemaining <= 0 && !IsFactoryCommanderGroupFull(psFactory)) { - if (isMission) + ProductionHaltReason reason = checkHaltOnMaxUnitsReached(psStructure, isMission); + + if (reason == NO_HALT) { - // put it in the mission list - psDroid = buildMissionDroid((DROID_TEMPLATE *)pSubject, - psStructure->pos.x, psStructure->pos.y, - psStructure->player); - if (psDroid) + bool bDroidPlaced = false; + + if (isMission) { - psDroid->secondaryOrder = psFactory->secondaryOrder; - psDroid->secondaryOrderPending = psDroid->secondaryOrder; - setFactorySecondaryState(psDroid, psStructure); - setDroidBase(psDroid, psStructure); - bDroidPlaced = true; + // put it in the mission list + psDroid = buildMissionDroid((DROID_TEMPLATE *)pSubject, + psStructure->pos.x, psStructure->pos.y, + psStructure->player); + if (psDroid) + { + psDroid->secondaryOrder = psFactory->secondaryOrder; + psDroid->secondaryOrderPending = psDroid->secondaryOrder; + setFactorySecondaryState(psDroid, psStructure); + setDroidBase(psDroid, psStructure); + bDroidPlaced = true; + } + } + else + { + // place it on the map + bDroidPlaced = structPlaceDroid(psStructure, (DROID_TEMPLATE *)pSubject, &psDroid); } - } - else - { - // place it on the map - bDroidPlaced = structPlaceDroid(psStructure, (DROID_TEMPLATE *)pSubject, &psDroid); - } - //script callback, must be called after factory was flagged as idle - if (bDroidPlaced) - { - //reset the start time - psFactory->timeStarted = ACTION_START_TIME; - psFactory->psSubject = nullptr; + //script callback, must be called after factory was flagged as idle + if (bDroidPlaced) + { + //reset the start time + psFactory->timeStarted = ACTION_START_TIME; + psFactory->psSubject = nullptr; - doNextProduction(psStructure, (DROID_TEMPLATE *)pSubject, ModeImmediate); + doNextProduction(psStructure, (DROID_TEMPLATE *)pSubject, ModeImmediate); - cbNewDroid(psStructure, psDroid); + cbNewDroid(psStructure, psDroid); + } + } + else if (reason == CONSTRUCTION_LIMIT && containsNonConstructionDroid(psStructure)) + { + // if there are other non construction droid entries in the production run + // Skip this entry (which is a construction droid) + ProductionRunEntry *nextEntry = nextProductionRunEntry(psStructure, (DROID_TEMPLATE *)pSubject, true); + if (nextEntry) + { + // TODO fix bug where truck is last one in the productionRun and it loops twice or something weird. smthn to do with pSubject? + doNextProduction(psStructure, (DROID_TEMPLATE *)pSubject, ModeImmediate); + } } } } @@ -3503,7 +3616,6 @@ static void aiUpdateStructure(STRUCTURE *psStructure, bool isMission) } } - /** Decides whether a structure should emit smoke when it's damaged */ static bool canSmoke(const STRUCTURE *psStruct) { @@ -3963,7 +4075,7 @@ std::vector fillStructureList(UDWORD _selectedPlayer, UDWORD int8_t *counter; if (asStructureStats[inc].type == REF_RESEARCH) { - counter = researchLabCurrMax; + counter = researchLabCurrMax; } else if (asStructureStats[inc].type == REF_FACTORY) { @@ -4038,7 +4150,7 @@ std::vector fillStructureList(UDWORD _selectedPlayer, UDWORD if (psBuilding->type == REF_RESEARCH_MODULE) { - //don't add to list if Research Facility not presently built + //don't add to list if Research Facility not presently built //or if all labs already have a module if (!researchLabCurrMax[0] || researchModules >= researchLabCurrMax[1]) { @@ -4333,7 +4445,7 @@ bool validLocation(BASE_STATS *psStats, Vector2i pos, uint16_t direction, unsign { STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y)); if (psStruct && (psStruct->pStructureType->type == REF_FACTORY || - psStruct->pStructureType->type == REF_VTOL_FACTORY) + psStruct->pStructureType->type == REF_VTOL_FACTORY) && psStruct->status == SS_BUILT && aiCheckAlliances(player, psStruct->player) && nextModuleToBuild(psStruct, -1) > 0) { @@ -4345,8 +4457,8 @@ bool validLocation(BASE_STATS *psStats, Vector2i pos, uint16_t direction, unsign if (TileHasStructure(worldTile(pos))) { STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y)); - if (psStruct && psStruct->pStructureType->type == REF_RESEARCH - && psStruct->status == SS_BUILT + if (psStruct && psStruct->pStructureType->type == REF_RESEARCH + && psStruct->status == SS_BUILT && aiCheckAlliances(player, psStruct->player) && nextModuleToBuild(psStruct, -1) > 0) { @@ -4358,8 +4470,8 @@ bool validLocation(BASE_STATS *psStats, Vector2i pos, uint16_t direction, unsign if (TileHasStructure(worldTile(pos))) { STRUCTURE const *psStruct = getTileStructure(map_coord(pos.x), map_coord(pos.y)); - if (psStruct && psStruct->pStructureType->type == REF_POWER_GEN - && psStruct->status == SS_BUILT + if (psStruct && psStruct->pStructureType->type == REF_POWER_GEN + && psStruct->status == SS_BUILT && aiCheckAlliances(player, psStruct->player) && nextModuleToBuild(psStruct, -1) > 0) { @@ -6086,7 +6198,6 @@ void cancelProduction(STRUCTURE *psBuilding, QUEUE_MODE mode, bool mayClearProdu delPowerRequest(psBuilding); } - /*set a factory's production run to hold*/ void holdProduction(STRUCTURE *psBuilding, QUEUE_MODE mode) {