Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -307,6 +307,7 @@ batchtrain = Shift ; Modifier to train units in batches massbarter = Shift ; Modifier to barter bunch of resources masstribute = Shift ; Modifier to tribute bunch of resources +assignall = Shift ; Modifier to assign all idle villagers to a selected task using the command button. noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit fulltradeswap = Shift ; Modifier to put the desired trade resource to 100% unloadtype = Shift ; Modifier to unload all units of type Index: binaries/data/mods/public/gui/session/input.js =================================================================== --- binaries/data/mods/public/gui/session/input.js +++ binaries/data/mods/public/gui/session/input.js @@ -236,19 +236,23 @@ target = ent; } - // decide between the following ordered actions - // if two actions are possible, the first one is taken - // so the most specific should appear first - var actions = Object.keys(g_UnitActions).slice(); + return determineActionOnEntity(target, selection); +} + +function determineActionOnEntity(target, selection) +{ + // Decide between the following ordered actions. + // If two actions are possible, the first one is taken, + // so the most specific should appear first. + let actions = Object.keys(g_UnitActions).slice(); actions.sort((a, b) => g_UnitActions[a].specificness - g_UnitActions[b].specificness); - var actionInfo = undefined; if (preSelectedAction != ACTION_NONE) { - for (var action of actions) + for (let action of actions) if (g_UnitActions[action].preSelectedActionCheck) { - var r = g_UnitActions[action].preSelectedActionCheck(target, selection); + let r = g_UnitActions[action].preSelectedActionCheck(target, selection); if (r) return r; } @@ -256,18 +260,18 @@ return { "type": "none", "cursor": "", "target": target }; } - for (var action of actions) + for (let action of actions) if (g_UnitActions[action].hotkeyActionCheck) { - var r = g_UnitActions[action].hotkeyActionCheck(target, selection); + let r = g_UnitActions[action].hotkeyActionCheck(target, selection); if (r) return r; } - for (var action of actions) + for (let action of actions) if (g_UnitActions[action].actionCheck) { - var r = g_UnitActions[action].actionCheck(target, selection); + let r = g_UnitActions[action].actionCheck(target, selection); if (r) return r; } 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 @@ -129,7 +129,7 @@ if (data.item.callback) data.item.callback(data.item); else - performCommand(data.unitEntStates, data.item.name); + performCommand(data.unitEntStates, data.item.name, data.item.data); }; data.countDisplay.caption = data.item.count || ""; @@ -137,7 +137,8 @@ data.button.enabled = g_IsObserver && data.item.name == "focus-rally" || controlsPlayer(data.player) && (data.item.name != "delete" || - data.unitEntStates.some(state => !isUndeletable(state))); + data.unitEntStates.some(state => !isUndeletable(state))) && + (!data.item.active || data.item.active == "true"); data.icon.sprite = "stretched:session/icons/" + data.item.icon; 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 @@ -232,7 +232,7 @@ Engine.HotkeyIsPressed("session.deselectgroup") || deselectGroup); } -function performCommand(entStates, commandName) +function performCommand(entStates, commandName, data) { if (!entStates.length) return; @@ -244,7 +244,7 @@ return; if (g_EntityCommands[commandName]) - g_EntityCommands[commandName].execute(entStates); + g_EntityCommands[commandName].execute(entStates, data); } function performAllyCommand(entity, commandName) @@ -355,6 +355,13 @@ }); } +function assignIdleVillager(data) +{ + let targets = Object.keys(data); + for (let target of targets) + g_UnitActions[data[target].action].execute(undefined, { "type": data[target].action, "target": +target }, data[target].entities, Engine.HotkeyIsPressed("session.queue")); +} + function unloadTemplate(template, owner) { Engine.PostNetworkCommand({ 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 @@ -1014,6 +1014,91 @@ */ var g_EntityCommands = { + "assign-idle-villager": { + "getInfo": function(entStates) + { + let workers = Engine.GuiInterfaceCall("FindIdleUnits", { + "idleClasses": ["Unit"], + "excludeUnits": [] + }); + if (!workers.length) + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.assignidlevillager") + + translate("There are no idle units."), + "icon": "production.png", + "active": "false" + }; + + let actions = Object.keys(g_UnitActions).slice(); + actions.sort((a, b) => g_UnitActions[a].specificness - g_UnitActions[b].specificness); + + // Object of the form: { "targetEntityID": { "action": toPerform, "entities": [toAssign] } }. + let assignmentData = {}; + for (let entState of entStates) + { + let maxWorkers; + let filteredWorkers; + + let chosenAction = determineActionOnEntity(entState.id, workers).type; + if (chosenAction == "none") + return false; + + filteredWorkers = workers.filter(worker => + ["preSelectedActionCheck", "hotkeyActionCheck", "actionCheck"].some(method => + g_UnitActions[chosenAction] && + g_UnitActions[chosenAction][method] && + g_UnitActions[chosenAction][method](entState.id, [worker]) + )); + if (maxWorkers) + filteredWorkers.slice(0, maxWorkers); + + // No appropriate idle workers. + if (!filteredWorkers.length) + continue; + + // If not all workers are desired select only the first. + // Should be improved to use the closest. + if (!Engine.HotkeyIsPressed("session.assignall")) + filteredWorkers = [filteredWorkers[0]]; + + assignmentData[entState.id] = { + "action": chosenAction, + "entities": filteredWorkers + }; + // Exclude the previously used workers. + workers = workers.filter(worker => filteredWorkers.indexOf(worker) == -1); + } + if (Object.keys(assignmentData).length == 1) + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.assignidlevillager") + + sprintf(translate("Assign an idle unit to %(action)s this entity."), { + "action": assignmentData[Object.keys(assignmentData)[0]].action + }), + "icon": "production.png", + "data": assignmentData + }; + + if (Object.keys(assignmentData).length) + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.assignidlevillager") + + translate("Assign an idle unit to work on these entities."), + "icon": "production.png", + "data": assignmentData + }; + + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.assignidlevillager") + + translate("There are no appropriate idle units that can be assigned."), + "icon": "production.png", + "active": "false" + }; + }, + "execute": function(entStates, data) + { + assignIdleVillager(data, Engine.HotkeyIsPressed("session.assignall")); + }, + }, + "unload-all": { "getInfo": function(entStates) {