Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -112,7 +112,7 @@ for (let command in g_EntityCommands) { - let info = g_EntityCommands[command].getInfo(unitEntStates); + let info = getCommandInfo(command, unitEntStates); if (info) { info.name = command; @@ -134,10 +134,7 @@ data.countDisplay.caption = data.item.count || ""; - data.button.enabled = - g_IsObserver && data.item.name == "focus-rally" || - controlsPlayer(data.player) && (data.item.name != "delete" || - data.unitEntStates.some(state => !isUndeletable(state))); + data.button.enabled = data.item.enabled == true; data.icon.sprite = "stretched:session/icons/" + data.item.icon; @@ -153,59 +150,6 @@ } }; -g_SelectionPanels.AllyCommand = { - "getMaxNumberOfItems": function() - { - return 2; - }, - "conflictsWith": ["Command"], - "getItems": function(unitEntStates) - { - let commands = []; - for (let command in g_AllyEntityCommands) - for (let state of unitEntStates) - { - let info = g_AllyEntityCommands[command].getInfo(state); - if (info) - { - info.name = command; - commands.push(info); - break; - } - } - return commands; - }, - "setupButton": function(data) - { - data.button.tooltip = data.item.tooltip; - - data.button.onPress = function() { - if (data.item.callback) - data.item.callback(data.item); - else - performAllyCommand(data.unitEntStates[0].id, data.item.name); - }; - - data.countDisplay.caption = data.item.count || ""; - - data.button.enabled = !!data.item.count; - - let grayscale = data.button.enabled ? "" : "grayscale:"; - data.icon.sprite = "stretched:" + grayscale + "session/icons/" + data.item.icon; - - let size = data.button.size; - // relative to the center ( = 50%) - size.rleft = 50; - size.rright = 50; - // offset from the center calculation, count on square buttons, so size.bottom is the width too - size.left = (data.i - data.numberOfItems / 2) * (size.bottom + 1); - size.right = size.left + size.bottom; - data.button.size = size; - - return true; - } -}; - g_SelectionPanels.Construction = { "getMaxNumberOfItems": function() { @@ -1232,7 +1176,6 @@ // UNIQUE PANES (importance doesn't matter) "Command", - "AllyCommand", "Queue", "Selection", ]; Index: binaries/data/mods/public/gui/session/selection_panels_helpers.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -240,30 +240,10 @@ if (!entStates.length) return; - // Don't check all entities, because we assume a player cannot - // select entities from more than one player - if (!controlsPlayer(entStates[0].player) && - !(g_IsObserver && commandName == "focus-rally")) - return; - - if (g_EntityCommands[commandName]) + if (getCommandInfo(commandName, entStates)) g_EntityCommands[commandName].execute(entStates); } -function performAllyCommand(entity, commandName) -{ - if (!entity) - return; - - let entState = GetEntityState(entity); - let playerState = GetSimState().players[Engine.GetPlayerID()]; - if (!playerState.isMutualAlly[entState.player] || g_IsObserver) - return; - - if (g_AllyEntityCommands[commandName]) - g_AllyEntityCommands[commandName].execute(entState); -} - function performFormation(entities, formationTemplate) { if (!entities) Index: binaries/data/mods/public/gui/session/selection_panels_middle/unit_ally_commands.xml =================================================================== --- binaries/data/mods/public/gui/session/selection_panels_middle/unit_ally_commands.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - Index: binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- binaries/data/mods/public/gui/session/unit_actions.js +++ binaries/data/mods/public/gui/session/unit_actions.js @@ -1155,8 +1155,17 @@ { let count = 0; for (let entState of entStates) - if (entState.garrisonHolder) + { + if (!entState.garrisonHolder) + continue; + + if (allowedPlayersCheck([entState], ["Player"])) count += entState.garrisonHolder.entities.length; + else + for (let entity of entState.garrisonHolder.entities) + if (allowedPlayersCheck([GetEntityState(entity)], ["Player"])) + ++count; + } if (!count) return false; @@ -1166,12 +1175,14 @@ translate("Unload All."), "icon": "garrison-out.png", "count": count, + "enabled": true }; }, "execute": function() { unloadAll(); }, + "allowedPlayers": ["Player", "Ally"] }, "delete": { @@ -1186,7 +1197,8 @@ translate("Use %(hotkey)s to avoid the confirmation dialog."), "session.noconfirmation" ), - "icon": "kill_small.png" + "icon": "kill_small.png", + "enabled": true } : { // Get all delete reasons and remove duplications @@ -1194,7 +1206,8 @@ .filter((reason, pos, self) => self.indexOf(reason) == pos && reason ).join("\n"), - "icon": "kill_small_disabled.png" + "icon": "kill_small_disabled.png", + "enabled": false }; }, "execute": function(entStates) @@ -1220,6 +1233,7 @@ else (new DeleteSelectionConfirmation(deleteSelection)).display(); }, + "allowedPlayers": ["Player"] }, "stop": { @@ -1231,7 +1245,8 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.stop") + translate("Abort the current order."), - "icon": "stop.png" + "icon": "stop.png", + "enabled": true }; }, "execute": function(entStates) @@ -1239,6 +1254,7 @@ if (entStates.length) stopUnits(entStates.map(entState => entState.id)); }, + "allowedPlayers": ["Player"] }, "garrison": { @@ -1250,7 +1266,8 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.garrison") + translate("Order the selected units to garrison in a structure or unit."), - "icon": "garrison.png" + "icon": "garrison.png", + "enabled": true }; }, "execute": function() @@ -1258,6 +1275,7 @@ inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_GARRISON; }, + "allowedPlayers": ["Player"] }, "unload": { @@ -1273,13 +1291,15 @@ return { "tooltip": translate("Unload"), - "icon": "garrison-out.png" + "icon": "garrison-out.png", + "enabled": true }; }, "execute": function() { unloadSelection(); }, + "allowedPlayers": ["Player"] }, "repair": { @@ -1291,7 +1311,8 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.repair") + translate("Order the selected units to repair a structure, ship, or siege engine."), - "icon": "repair.png" + "icon": "repair.png", + "enabled": true }; }, "execute": function() @@ -1299,6 +1320,7 @@ inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_REPAIR; }, + "allowedPlayers": ["Player"] }, "focus-rally": { @@ -1310,7 +1332,8 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "camera.rallypointfocus") + translate("Focus on Rally Point."), - "icon": "focus-rally.png" + "icon": "focus-rally.png", + "enabled": true }; }, "execute": function(entStates) @@ -1334,6 +1357,7 @@ if (focusTarget) Engine.CameraMoveTo(focusTarget.x, focusTarget.z); }, + "allowedPlayers": ["Player", "Observer"] }, "back-to-work": { @@ -1345,13 +1369,15 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.backtowork") + translate("Back to Work"), - "icon": "back-to-work.png" + "icon": "back-to-work.png", + "enabled": true }; }, "execute": function() { backToWork(); }, + "allowedPlayers": ["Player"] }, "add-guard": { @@ -1364,7 +1390,8 @@ return { "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.guard") + translate("Order the selected units to guard a structure or unit."), - "icon": "add-guard.png" + "icon": "add-guard.png", + "enabled": true }; }, "execute": function() @@ -1372,6 +1399,7 @@ inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_GUARD; }, + "allowedPlayers": ["Player"] }, "remove-guard": { @@ -1382,13 +1410,15 @@ return { "tooltip": translate("Remove guard"), - "icon": "remove-guard.png" + "icon": "remove-guard.png", + "enabled": true }; }, "execute": function() { removeGuard(); }, + "allowedPlayers": ["Player"] }, "select-trading-goods": { @@ -1399,13 +1429,15 @@ return { "tooltip": translate("Barter & Trade"), - "icon": "economics.png" + "icon": "economics.png", + "enabled": true }; }, "execute": function() { g_TradeDialog.toggle(); }, + "allowedPlayers": ["Player"] }, "patrol": { @@ -1418,7 +1450,8 @@ "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.patrol") + translate("Patrol") + "\n" + translate("Attack all encountered enemy units while avoiding structures."), - "icon": "patrol.png" + "icon": "patrol.png", + "enabled": true }; }, "execute": function() @@ -1426,6 +1459,7 @@ inputState = INPUT_PRESELECTEDACTION; preSelectedAction = ACTION_PATROL; }, + "allowedPlayers": ["Player"] }, "share-dropsite": { @@ -1436,7 +1470,7 @@ if (!sharableEntities.length) return false; - // Returns if none of the entities belong to a player with a mutual ally + // Returns if none of the entities belong to a player with a mutual ally. if (entStates.every(entState => !GetSimState().players[entState.player].isMutualAlly.some( (isAlly, playerId) => isAlly && playerId != entState.player))) return false; @@ -1444,87 +1478,62 @@ return sharableEntities.some(entState => !entState.resourceDropsite.shared) ? { "tooltip": translate("Press to allow allies to use this dropsite"), - "icon": "locked_small.png" + "icon": "locked_small.png", + "enabled": true } : { "tooltip": translate("Press to prevent allies from using this dropsite"), - "icon": "unlocked_small.png" + "icon": "unlocked_small.png", + "enabled": true }; }, "execute": function(entStates) { let sharableEntities = entStates.filter( entState => entState.resourceDropsite && entState.resourceDropsite.sharable); - Engine.PostNetworkCommand({ - "type": "set-dropsite-sharing", - "entities": sharableEntities.map(entState => entState.id), - "shared": sharableEntities.some(entState => !entState.resourceDropsite.shared) - }); + if (sharableEntities) + Engine.PostNetworkCommand({ + "type": "set-dropsite-sharing", + "entities": sharableEntities.map(entState => entState.id), + "shared": sharableEntities.some(entState => !entState.resourceDropsite.shared) + }); }, - } -}; + "allowedPlayers": ["Player"] + }, -var g_AllyEntityCommands = -{ - "unload-all": { - "getInfo": function(entState) + "is-dropsite-shared": { + "getInfo": function(entStates) { - if (!entState.garrisonHolder) + let sharableEntities = entStates.filter( + entState => entState.resourceDropsite && entState.resourceDropsite.sharable); + if (!sharableEntities.length) return false; let player = Engine.GetPlayerID(); - - let count = 0; - for (let ent in g_Selection.selected) - { - let selectedEntState = GetEntityState(+ent); - if (!selectedEntState.garrisonHolder) - continue; - - for (let entity of selectedEntState.garrisonHolder.entities) - { - let state = GetEntityState(entity); - if (state.player == player) - ++count; - } - } - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unload") + - translate("Unload All."), - "icon": "garrison-out.png", - "count": count, - }; - }, - "execute": function(entState) - { - unloadAll(); - }, - }, - - "share-dropsite": { - "getInfo": function(entState) - { - if (Engine.GetPlayerID() == -1 || - !GetSimState().players[Engine.GetPlayerID()].hasSharedDropsites || - !entState.resourceDropsite || !entState.resourceDropsite.sharable) + if (player != -1 && entStates.every(entState => + !GetSimState().players[player].hasSharedDropsites || + !entState.resourceDropsite || !entState.resourceDropsite.sharable)) return false; - if (entState.resourceDropsite.shared) + if (!entStates.every(entState => entState.resourceDropsite.shared)) return { - "tooltip": translate("You are allowed to use this dropsite"), - "icon": "unlocked_small.png" + "tooltip": translate("The use of this dropsite is prohibited"), + "icon": "locked_small.png", + "enabled": false }; return { - "tooltip": translate("The use of this dropsite is prohibited"), - "icon": "locked_small.png" + "tooltip": player == -1 ? translate("Allies are allowed to use this dropsite.") : + translate("You are allowed to use this dropsite"), + "icon": "unlocked_small.png", + "enabled": false }; }, "execute": function(entState) { - // This command button is always disabled + // This command button is always disabled. }, + "allowedPlayers": ["Ally", "Observer"] } }; @@ -1540,6 +1549,29 @@ return false; } +/** + * Checks whether the entities have the right diplomatic status + * with respect to the currently active player. + * Also "Observer" can be used. + * + * @param {Object[]} entStates - An array containing the entity states to check. + * @param {string[]} validPlayers - An array containing the diplomatic statuses. + * + * @return {boolean} - Whether the currently active player is allowed. + */ +function allowedPlayersCheck(entStates, validPlayers) +{ + // Assume we can only select entities from one player, + // or it does not matter (e.g. observer). + let targetState = entStates[0]; + let playerState = GetSimState().players[Engine.GetPlayerID()]; + + return validPlayers.some(player => + player == "Observer" && g_IsObserver || + player == "Player" && controlsPlayer(targetState.player) || + playerState && playerState["is" + player] && playerState["is" + player][targetState.player]); +} + function hasClass(entState, className) { // note: use the functions in globalscripts/Templates.js for more versatile matching @@ -1625,6 +1657,13 @@ return undefined; } +function getCommandInfo(command, entStates) +{ + return entStates && g_EntityCommands[command] && + allowedPlayersCheck(entStates, g_EntityCommands[command].allowedPlayers) && + g_EntityCommands[command].getInfo(entStates); +} + function getActionInfo(action, target, selection) { if (!selection || !selection.length || !GetEntityState(selection[0])) Index: binaries/data/mods/public/gui/session/unit_commands.js =================================================================== --- binaries/data/mods/public/gui/session/unit_commands.js +++ binaries/data/mods/public/gui/session/unit_commands.js @@ -10,7 +10,6 @@ "Barter": 0, "Construction": 0, "Command": 0, - "AllyCommand": 0, "Stance": 0, "Gate": 0, "Pack": 0, @@ -128,16 +127,18 @@ for (let panel in g_SelectionPanels) g_SelectionPanels[panel].used = false; - // If the selection is friendly units, add the command panels - // Get player state to check some constraints - // e.g. presence of a hero or build limits + // e.g. presence of a hero or build limits. let playerStates = GetSimState().players; let playerState = playerStates[Engine.GetPlayerID()]; // Always show selection. setupUnitPanel("Selection", entStates, playerStates[entStates[0].player]); + // Command panel always shown for it can contain commands + // for which the entity does not need to be owned. + setupUnitPanel("Command", entStates, playerState); + if (g_IsObserver || entStates.every(entState => controlsPlayer(entState.player) && (!entState.identity || entState.identity.controllable)) || @@ -156,18 +157,17 @@ supplementalDetailsPanel.hidden = false; commandsPanel.hidden = false; } - else if (playerState.isMutualAlly[entStates[0].player]) // owned by allied player + else 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); - setupUnitPanel("AllyCommand", entStates, playerState); supplementalDetailsPanel.hidden = !g_SelectionPanels.Garrison.used; commandsPanel.hidden = true; } - else // owned by another player + else { supplementalDetailsPanel.hidden = true; commandsPanel.hidden = true;