From a6460189edb81d8b6a4465619f9b1ec0b3b7ee28 Mon Sep 17 00:00:00 2001 From: guerringuerrin Date: Wed, 18 Mar 2026 19:05:12 -0300 Subject: [PATCH] Prioritize least busy buildings and enforce max training queue limit getTotalQueueTime(queue) calculates total remaining queue time of a production building. getBuildingsSortedByQueueTime(entities), sorts building selection by their queued training time. --- .../data/mods/public/gui/session/input.js | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 2585b80c16..b68f4f0b26 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -1596,6 +1596,30 @@ 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. + */ +function getBuildingsSortedByQueueTime(entities) +{ + return entities.map(entity => + { + const queue = GetEntityState(entity)?.production?.queue || []; + return { + "entity": entity, + "totalQueueTime": getTotalQueueTime(queue) + }; + }).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 +1652,7 @@ function addTrainingToQueue(selection, trainEntType, playerState) if (!decrement) template = GetTemplateData(trainEntType); + const sortedBuildings = getBuildingsSortedByQueueTime(appropriateBuildings); // Batch training only possible if we can train at least 2 units. if (Engine.HotkeyIsPressed("session.batchtrain") && (canBeAddedCount == undefined || canBeAddedCount > 1)) { @@ -1638,8 +1663,8 @@ function addTrainingToQueue(selection, trainEntType, playerState) // If the order changed, we have a new selection and we should create a new batch. // If we're already creating a batch of this unit (in the same structure(s)), then just extend it // (if training limits allow). - if (g_BatchTrainingEntities.length == selection.length && - g_BatchTrainingEntities.every((ent, i) => ent == selection[i]) && + if (g_BatchTrainingEntities.length == sortedBuildings.length && + g_BatchTrainingEntities.every((ent, i) => ent == sortedBuildings[i]) && g_BatchTrainingType == trainEntType) { if (decrement) @@ -1649,7 +1674,7 @@ function addTrainingToQueue(selection, trainEntType, playerState) inputState = INPUT_NORMAL; } else if (canBeAddedCount == undefined || - canBeAddedCount > g_NumberOfBatches * getBatchTrainingSize() * appropriateBuildings.length) + canBeAddedCount > g_NumberOfBatches * getBatchTrainingSize() * sortedBuildings.length) { if (Engine.GuiInterfaceCall("GetNeededResources", { "cost": multiplyEntityCosts(template, (g_NumberOfBatches + 1) * getBatchTrainingSize()) @@ -1671,14 +1696,14 @@ function addTrainingToQueue(selection, trainEntType, playerState) return; inputState = INPUT_BATCHTRAINING; - g_BatchTrainingEntities = selection; + g_BatchTrainingEntities = sortedBuildings; g_BatchTrainingType = trainEntType; g_BatchTrainingEntityAllowedCount = canBeAddedCount; g_NumberOfBatches = 1; } else { - let buildingsForTraining = appropriateBuildings; + let buildingsForTraining = sortedBuildings; if (canBeAddedCount !== undefined) buildingsForTraining = buildingsForTraining.slice(0, canBeAddedCount); Engine.PostNetworkCommand({ @@ -1734,6 +1759,7 @@ function flushTrainingBatch() { const batchedSize = g_NumberOfBatches * getBatchTrainingSize(); const appropriateBuildings = getBuildingsWhichCanTrainEntity(g_BatchTrainingEntities, g_BatchTrainingType); + 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 +1768,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 +1779,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 +1788,7 @@ function flushTrainingBatch() else Engine.PostNetworkCommand({ "type": "train", - "entities": appropriateBuildings, + "entities": sortedBuildings, "template": g_BatchTrainingType, "count": batchedSize, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront")