diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 2585b80c16..c446246958 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -1596,6 +1596,34 @@ function updateDefaultBatchSize() g_BatchSize = getDefaultBatchTrainingSize(); } +/** + * Calculates the total remaining production queue time of a building. + */ +function getTotalQueueTime(queue) +{ + return queue.reduce((sum, item) => sum + (item.timeRemaining ?? item.timeTotal ?? 0), 0); +} + +/** + * Returns the given production building selection sorted by their current + * queued training time, from lowest to highest. + * Invalid entities or entities without a valid production queue are filtered out. + */ +function getBuildingsSortedByQueueTime(entities) +{ + return entities.map(entity => + { + const state = GetEntityState(entity); + const queue = state?.production?.queue; + if (!Array.isArray(queue)) + return null; + return { + "entity": entity, + "totalQueueTime": getTotalQueueTime(queue) + }; + }).filter(data => data !== null).sort((a, b) => a.totalQueueTime - b.totalQueueTime).map(building => building.entity); +} + /** * Add the unit shown at position to the training queue for all entities in the selection. * @param {number} position - The position of the template to train. @@ -1628,6 +1656,7 @@ function addTrainingToQueue(selection, trainEntType, playerState) if (!decrement) template = GetTemplateData(trainEntType); + // Batch training only possible if we can train at least 2 units. if (Engine.HotkeyIsPressed("session.batchtrain") && (canBeAddedCount == undefined || canBeAddedCount > 1)) { @@ -1678,14 +1707,14 @@ function addTrainingToQueue(selection, trainEntType, playerState) } else { - let buildingsForTraining = appropriateBuildings; + let sortedBuildingsForTraining = getBuildingsSortedByQueueTime(appropriateBuildings); if (canBeAddedCount !== undefined) - buildingsForTraining = buildingsForTraining.slice(0, canBeAddedCount); + sortedBuildingsForTraining = sortedBuildingsForTraining.slice(0, canBeAddedCount); Engine.PostNetworkCommand({ "type": "train", "template": trainEntType, "count": 1, - "entities": buildingsForTraining, + "entities": sortedBuildingsForTraining, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront") }); } @@ -1734,6 +1763,12 @@ function flushTrainingBatch() { const batchedSize = g_NumberOfBatches * getBatchTrainingSize(); const appropriateBuildings = getBuildingsWhichCanTrainEntity(g_BatchTrainingEntities, g_BatchTrainingType); + + // Sort buildings by queued training time so that, if some training + // commands fail due to resource or entity limits, units are assigned + // to the least busy buildings first. + const sortedBuildings = getBuildingsSortedByQueueTime(appropriateBuildings); + // If training limits don't allow us to train batchedSize in each appropriate structure. if (g_BatchTrainingEntityAllowedCount !== undefined && g_BatchTrainingEntityAllowedCount < batchedSize * appropriateBuildings.length) @@ -1742,7 +1777,7 @@ function flushTrainingBatch() const buildingsCountToTrainFullBatch = Math.floor(g_BatchTrainingEntityAllowedCount / batchedSize); Engine.PostNetworkCommand({ "type": "train", - "entities": appropriateBuildings.slice(0, buildingsCountToTrainFullBatch), + "entities": sortedBuildings.slice(0, buildingsCountToTrainFullBatch), "template": g_BatchTrainingType, "count": batchedSize, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront") @@ -1753,7 +1788,7 @@ function flushTrainingBatch() if (remainer) Engine.PostNetworkCommand({ "type": "train", - "entities": [appropriateBuildings[buildingsCountToTrainFullBatch]], + "entities": [sortedBuildings[buildingsCountToTrainFullBatch]], "template": g_BatchTrainingType, "count": remainer, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront") @@ -1762,7 +1797,7 @@ function flushTrainingBatch() else Engine.PostNetworkCommand({ "type": "train", - "entities": appropriateBuildings, + "entities": sortedBuildings, "template": g_BatchTrainingType, "count": batchedSize, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront") diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js index 4ff9444444..d8ee139c9d 100644 --- a/binaries/data/mods/public/gui/session/selection_panels.js +++ b/binaries/data/mods/public/gui/session/selection_panels.js @@ -1035,8 +1035,7 @@ g_SelectionPanels.Training = { data.button.onPress = function() { - if (!neededResources) - addTrainingToQueue(unitIds, data.item, data.playerState); + addTrainingToQueue(unitIds, data.item, data.playerState); }; const showTemplateFunc = () => { showTemplateDetails(data.item, data.playerState.civ); };