0ad/binaries/data/mods/public/gui/session/unit_commands.js

242 lines
7.4 KiB
JavaScript
Raw Normal View History

// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {
"Selection": 0,
"Queue": 0,
"Formation": 0,
"Garrison": 0,
"Training": 0,
"Research": 0,
"Alert": 0,
"Barter": 0,
"Construction": 0,
"Command": 0,
"Stance": 0,
"Gate": 0,
"Pack": 0,
"Upgrade": 0
};
/**
* Set the position of a panel object according to the index,
* from left to right, from top to bottom.
* Will wrap around to subsequent rows if the index
* is larger than rowLength.
*/
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
function setPanelObjectPosition(object, index, rowLength, vMargin = 1, hMargin = 1, vOffset = 0, hOffset = 0)
{
const oWidth = object.size.right - object.size.left;
const oHeight = object.size.bottom - object.size.top;
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
const left = (index % rowLength) * (oWidth + hMargin) + hOffset;
const top = (Math.floor(index / rowLength)) * (oHeight + vMargin) + vOffset;
Object.assign(object.size, {
// horizontal position
"left": left,
"right": left + oWidth,
// vertical position
"top": top,
"bottom": top + oHeight
});
}
/**
* Helper function for updateUnitCommands; sets up "unit panels"
* (i.e. panels with rows of icons) for the currently selected unit.
*
* @param guiName Short identifier string of this panel. See g_SelectionPanels.
* @param unitEntStates Entity states of the selected units
* @param playerState Player state
*/
function setupUnitPanel(guiName, unitEntStates, playerState)
{
if (!g_SelectionPanels[guiName])
{
error("unknown guiName used '" + guiName + "'");
return;
}
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
const items = g_SelectionPanels[guiName].getItems(unitEntStates) || [];
const numberOfItems = Math.min(items.length, g_SelectionPanels[guiName].getMaxNumberOfItems());
const rowLength = g_SelectionPanels[guiName].rowLength || 8;
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
if (numberOfItems && g_SelectionPanels[guiName].resizePanel)
g_SelectionPanels[guiName].resizePanel(numberOfItems, rowLength);
for (let i = 0; i < numberOfItems; ++i)
{
const data = {
"i": i,
"item": items[i],
"playerState": playerState,
"player": unitEntStates[0].player,
"unitEntStates": unitEntStates,
"rowLength": rowLength,
"numberOfItems": numberOfItems,
// depending on the XML, some of the GUI objects may be undefined
"button": Engine.TryGetGUIObjectByName("unit" + guiName + "Button[" + i + "]"),
"icon": Engine.TryGetGUIObjectByName("unit" + guiName + "Icon[" + i + "]"),
"guiSelection": Engine.TryGetGUIObjectByName("unit" + guiName + "Selection[" + i + "]"),
"countDisplay": Engine.TryGetGUIObjectByName("unit" + guiName + "Count[" + i + "]")
};
if (data.button)
{
data.button.hidden = false;
data.button.enabled = true;
data.button.tooltip = "";
data.button.caption = "";
}
if (g_SelectionPanels[guiName].setupButton &&
!g_SelectionPanels[guiName].setupButton(data))
continue;
// TODO: we should require all entities to have icons, so this case never occurs
if (data.icon && !data.icon.sprite)
data.icon.sprite = "BackgroundBlack";
}
// Hide any buttons we're no longer using
for (let i = numberOfItems; i < g_unitPanelButtons[guiName]; ++i)
if (g_SelectionPanels[guiName].hideItem)
g_SelectionPanels[guiName].hideItem(i, rowLength);
else
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
{
const button = Engine.TryGetGUIObjectByName("unit" + guiName + "Button[" + i + "]");
if (button) button.hidden = true;
}
g_unitPanelButtons[guiName] = numberOfItems;
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
g_SelectionPanels[guiName].used = numberOfItems > 0;
}
/**
* Updates the selection panels where buttons are supposed to
* depend on the context.
* Runs in the main session loop via updateSelectionDetails().
* Delegates to setupUnitPanel to set up individual subpanels,
* appropriately activated depending on the selected unit's state.
*
* @param entStates Entity states of the selected units
* @param supplementalDetailsPanel Reference to the
* "supplementalSelectionDetails" GUI Object
* @param commandsPanel Reference to the "commandsPanel" GUI Object
*/
function updateUnitCommands(entStates, supplementalDetailsPanel, commandsPanel)
{
for (const panel in g_SelectionPanels)
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
{
g_SelectionPanels[panel].used = false;
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
g_SelectionPanels[panel].reset?.();
}
// Get player state to check some constraints
// e.g. presence of a hero or build limits.
const playerStates = GetSimState().players;
const playerState = playerStates[Engine.GetPlayerID()];
if (g_IsObserver || entStates.every(entState =>
Optimise GetEntityState GetEntityState is a performance-critical function in the GUI when a large number of units are selected. The goal of this patch is to increase the efficiency of it without modifying the returned states visible to rest of the GUI in any way (it renames a few properties of entities states or moves them around, but the information they contain and the way to access it remain the complete same) As explained the comments, certain parts (the template data) of an entity state are "predictable" and can be reused between entities with the same template. What one has to account for, however, is that values of the template data can be modified. This happens on two different levels: Firstly, player-wide modifications, which apply to all entities owned by that player. This includes stuff like bonuses from researched techs, civs bonuses, team bonuses. And secondly, entity-local modifications, which apply to individual entities. This includes buffs or debuffs from status effects or auras (of other entities). So we can construct a whole entity state by first just computing the dynamic state (stuff like current hitpoints, which differs from entity to entity) and then adding the (potentially already cached) template data to it, which can be reused between entities with the same template and owning player. And then overwrite the values affected by entity-local modifications, which are usually just a few and even they can be cached and reused for entities with the same owning player, template, and entity-local modifications (identified by the "modifications ID"). This saves the effort of retrieving/computing a ton of data each turn and also saves the time it takes the engine to clone the data from the simulation to the GUI (as the return value of the GUI interface call), which previously took just as long as retrieving the entity states themselves. Since only the dynamic state is read from the components, a number of small getter methods have become unused; they are kept (for now at least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
controlsPlayer(entState.player) && entState.controllable) ||
playerState.controlsAll)
{
for (const guiName of g_PanelsOrder)
{
More versatile placement of research buttons This patch allows techs to optionally define a property "placeBelow" specifying a combination of class requirements. The selection panel then tries to place their research button below the training button whose unit matches these classes -- if there is only and exactly one. This is useful for techs only affecting a single type of unit: it e.g. allows always placing the fishing nets research button below the fishing boats training button. To prevent duplicating information in "placeBelow" (e.g. from "affects"), it can also be set to two magic values: "{AffectedUnit}" and "{UnlockedUnit}". In practice, many techs use those, but class combination remain useful for granular control in very specific cases. Regarding tech pairs, as explained in a comment, their two research buttons can now be placed in three different arrangements (with descending preference): 1. Vertically below a single training button. 2. Horizontally below two adjacent training buttons. 3. Horizontally adjacent in the bottom row (below no training buttons). Vertically in the third and fourth rows (below no training button) -- how it was previously -- is no longer done as it'd conflict with and doesn't fit the new layout and the clear separation between "generic" and "specific" techs. Whenever a research button can't be placed in their preferred location because it is already occupied, the code simply falls back to the default position in the bottom row. This patch also adds a new game option to hide the new small arrows above the research buttons in order to give more experienced players who already know the techs well the possibility to reduce visual clutter. By default, the arrows are shown.
2025-09-24 07:52:59 -07:00
if (!g_SelectionPanels[guiName].conflictsWith ||
g_SelectionPanels[guiName].conflictsWith.every(p => !g_SelectionPanels[p].used))
setupUnitPanel(guiName, entStates, playerStates[entStates[0].player]);
}
supplementalDetailsPanel.hidden = false;
commandsPanel.hidden = false;
}
else
{
// Always show what entities are selected, no matter if they can be controlled.
setupUnitPanel("Selection", entStates, playerStates[entStates[0].player]);
// Always show the commands since they might not require the entities to be owned.
// TODO: This panel here is NOT related to the commandsPanel GUI object. The naming should be improved.
setupUnitPanel("Command", entStates, playerState);
if (playerState.isMutualAlly[entStates[0].player])
{
// TODO if there's a second panel needed for a different player
// we should consider adding the players list to g_SelectionPanels
setupUnitPanel("Garrison", entStates, playerState);
supplementalDetailsPanel.hidden = !g_SelectionPanels.Garrison.used;
commandsPanel.hidden = true;
}
else
{
supplementalDetailsPanel.hidden = true;
commandsPanel.hidden = true;
}
}
// Hides / unhides Unit Panels (panels should be grouped by type, not by order, but we will leave that for another time)
for (const panelName in g_SelectionPanels)
Engine.GetGUIObjectByName("unit" + panelName + "Panel").hidden = !g_SelectionPanels[panelName].used;
}
// Force hide commands panels
function hideUnitCommands()
{
for (var panelName in g_SelectionPanels)
Engine.GetGUIObjectByName("unit" + panelName + "Panel").hidden = true;
}
// Get all of the available entities which can be trained by the selected entities
function getAllTrainableEntities(selection)
{
let trainableEnts = [];
// Get all buildable and trainable entities
for (const ent of selection)
{
const state = GetEntityState(ent);
if (state?.trainer?.entities?.length)
{
if (!state.production)
warn("Trainer without Production Queue found: " + ent + ".");
trainableEnts = trainableEnts.concat(state.trainer.entities);
}
}
// Remove duplicates
removeDupes(trainableEnts);
return trainableEnts;
}
function getAllTrainableEntitiesFromSelection()
{
if (!g_allTrainableEntities)
g_allTrainableEntities = getAllTrainableEntities(g_Selection.toList());
return g_allTrainableEntities;
}
// Get all of the available entities which can be built by the selected entities
function getAllBuildableEntities(selection)
{
return Engine.GuiInterfaceCall("GetAllBuildableEntities", { "entities": selection });
}
function getAllBuildableEntitiesFromSelection()
{
if (!g_allBuildableEntities)
g_allBuildableEntities = getAllBuildableEntities(g_Selection.toList());
return g_allBuildableEntities;
}
function getNumberOfRightPanelButtons()
{
var sum = 0;
for (const prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
if (g_SelectionPanels[prop].used)
sum += g_unitPanelButtons[prop];
return sum;
}