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.
This commit is contained in:
guerringuerrin 2026-03-18 19:05:12 -03:00
parent 32e5520507
commit ffc8d80428

View file

@ -1620,14 +1620,14 @@ function addTrainingByPosition(position)
function addTrainingToQueue(selection, trainEntType, playerState)
{
const appropriateBuildings = getBuildingsWhichCanTrainEntity(selection, trainEntType);
const canBeAddedCount = getEntityLimitAndCount(playerState, trainEntType).canBeAddedCount;
const decrement = Engine.HotkeyIsPressed("selection.remove");
let template;
if (!decrement)
template = GetTemplateData(trainEntType);
// Sort the buildings by their current queue length (time-based) before training
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 +1638,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 +1649,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,16 +1671,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;
if (canBeAddedCount !== undefined)
buildingsForTraining = buildingsForTraining.slice(0, canBeAddedCount);
const buildingsForTraining = sortedBuildings.slice(0, canBeAddedCount || sortedBuildings.length);
Engine.PostNetworkCommand({
"type": "train",
"template": trainEntType,
@ -1733,40 +1731,65 @@ function getTrainingStatus(selection, trainEntType, playerState)
function flushTrainingBatch()
{
const batchedSize = g_NumberOfBatches * getBatchTrainingSize();
// Get the buildings that can train the current batch type
const appropriateBuildings = getBuildingsWhichCanTrainEntity(g_BatchTrainingEntities, g_BatchTrainingType);
// If training limits don't allow us to train batchedSize in each appropriate structure.
// Sort buildings by queue length
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)
{
// Train as many full batches as we can.
// Train as many full batches as we can
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")
});
// Train remainer in one more structure.
// Train remainder in one more structure
const remainer = g_BatchTrainingEntityAllowedCount % batchedSize;
if (remainer)
Engine.PostNetworkCommand({
"type": "train",
"entities": [appropriateBuildings[buildingsCountToTrainFullBatch]],
"entities": [sortedBuildings[buildingsCountToTrainFullBatch]],
"template": g_BatchTrainingType,
"count": remainer,
"pushFront": Engine.HotkeyIsPressed("session.pushorderfront")
});
}
else
{
// Train full batch in all selected buildings
Engine.PostNetworkCommand({
"type": "train",
"entities": appropriateBuildings,
"entities": sortedBuildings,
"template": g_BatchTrainingType,
"count": batchedSize,
"pushFront": Engine.HotkeyIsPressed("session.pushorderfront")
});
}
}
function getTotalQueueTime(queue)
{
return queue.reduce((sum, item) => sum + (item.timeRemaining ?? item.timeTotal ?? 0), 0);
}
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);
}
function performGroup(action, groupId)