Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -353,6 +353,7 @@ toggledefaultformation = "" ; Switch between null default formation and the last default formation used (defaults to "box") flare = K ; Modifier to send a flare to your allies flareactivate = "" ; Modifier to activate the mode to send a flare to your allies +calltoarms = "" ; Modifier to call the selected units to the arms. ; Overlays showstatusbars = Tab ; Toggle display of status bars devcommands.toggle = "Alt+D" ; Toggle developer commands panel Index: ps/trunk/binaries/data/mods/public/gui/hotkeys/spec/ingame.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/hotkeys/spec/ingame.json +++ ps/trunk/binaries/data/mods/public/gui/hotkeys/spec/ingame.json @@ -174,6 +174,10 @@ "session.flareactivate": { "name": "Flare (toggle)", "desc": "Set the cursor to Flare. The hotkey can be released." + }, + "session.calltoarms": { + "name": "Call to arms", + "desc": "Send the selected units on attack move to the specified location after dropping resources." } } } Index: ps/trunk/binaries/data/mods/public/gui/session/input.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/input.js +++ ps/trunk/binaries/data/mods/public/gui/session/input.js @@ -18,6 +18,7 @@ const ACTION_GUARD = 3; const ACTION_PATROL = 4; const ACTION_OCCUPY_TURRET = 5; +const ACTION_CALLTOARMS = 6; var preSelectedAction = ACTION_NONE; const INPUT_NORMAL = 0; Index: ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js +++ ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js @@ -246,6 +246,48 @@ "specificness": 10, }, + "call-to-arms": { + "execute": function(target, action, selection, queued, pushFront) + { + let targetClasses; + if (Engine.HotkeyIsPressed("session.attackmoveUnit")) + targetClasses = { "attack": ["Unit"] }; + else + targetClasses = { "attack": ["Unit", "Structure"] }; + Engine.PostNetworkCommand({ + "type": "call-to-arms", + "entities": selection, + "target": target, + "targetClasses": targetClasses, + "queued": queued, + "pushFront": pushFront, + "allowCapture": true, + "formation": g_AutoFormation.getNull() + }); + return true; + }, + "getActionInfo": function(entState, targetState) + { + return { "possible": !!entState.unitAI }; + }, + "actionCheck": function(target, selection) + { + const actionInfo = getActionInfo("call-to-arms", target, selection); + return actionInfo.possible && { + "type": "call-to-arms", + "cursor": "action-attack", + "target": target, + "firstAbleEntity": actionInfo.entity + }; + }, + "preSelectedActionCheck": function(target, selection) + { + return preSelectedAction == ACTION_CALLTOARMS && + this.actionCheck(target, selection); + }, + "specificness": 50, + }, + "patrol": { "execute": function(target, action, selection, queued, pushFront) @@ -1423,6 +1465,27 @@ "allowedPlayers": ["Player"] }, + "call-to-arms": { + "getInfo": function(entStates) + { + const classes = ["Soldier", "Warship", "Siege", "Healer"]; + if (entStates.every(entState => !MatchesClassList(entState.identity.classes, classes))) + return false; + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.calltoarms") + + translate("Send the selected units on attack move to the specified location after dropping resources."), + "icon": "call-to-arms.png", + "enabled": true + }; + }, + "execute": function(entStates) + { + inputState = INPUT_PRESELECTEDACTION; + preSelectedAction = ACTION_CALLTOARMS; + }, + "allowedPlayers": ["Player"] + }, + "garrison": { "getInfo": function(entStates) { Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -556,6 +556,17 @@ return ACCEPT_ORDER; }, + "Order.DropAtNearestDropSite": function(msg) { + const cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + if (!cmpResourceGatherer) + return this.FinishOrder(); + const nearby = this.FindNearestDropsite(cmpResourceGatherer.GetMainCarryingType()); + if (!nearby) + return this.FinishOrder(); + this.ReturnResource(nearby, false, true); + return ACCEPT_ORDER; + }, + "Order.ReturnResource": function(msg) { if (this.CheckTargetRange(msg.data.target, IID_ResourceGatherer)) this.SetNextState("INDIVIDUAL.RETURNRESOURCE.DROPPINGRESOURCES"); @@ -960,6 +971,13 @@ return ACCEPT_ORDER; }, + "Order.DropAtNearestDropSite": function(msg) { + this.CallMemberFunction("DropAtNearestDropSite", [false, false]); + + this.SetNextState("MEMBER"); + return ACCEPT_ORDER; + }, + "IDLE": { "enter": function(msg) { // Turn rearrange off. Otherwise, if the formation is idle @@ -5233,6 +5251,9 @@ case "Stop": return []; + case "DropAtNearestDropSite": + break; + default: error("GetTargetPositions: Unrecognised order type '"+order.type+"'"); return []; @@ -5409,6 +5430,15 @@ }; /** + * The unit will drop all resources at the closest dropsite. If this unit is no gatherer or + * no dropsite is available, it will do nothing. + */ +UnitAI.prototype.DropAtNearestDropSite = function(queued, pushFront) +{ + this.AddOrder("DropAtNearestDropSite", { "force": true }, queued, pushFront); +}; + +/** * Adds walk-to-target order to queue, this only occurs in response * to a player order, and so is forced. */ Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js @@ -279,6 +279,27 @@ } }, + "call-to-arms": function(player, cmd, data) + { + const unitsToMove = data.entities.filter(ent => + MatchesClassList(Engine.QueryInterface(ent, IID_Identity).GetClassesList(), + ["Soldier", "Warship", "Siege", "Healer"]) + ); + GetFormationUnitAIs(unitsToMove, player, cmd, data.formation).forEach(cmpUnitAI => { + const target = cmd.target; + if (cmd.pushFront) + { + cmpUnitAI.WalkAndFight(target.x, target.z, cmd.targetClasses, cmd.allowCapture, false, cmd.pushFront); + cmpUnitAI.DropAtNearestDropSite(false, cmd.pushFront); + } + else + { + cmpUnitAI.DropAtNearestDropSite(cmd.queued, false) + cmpUnitAI.WalkAndFight(target.x, target.z, cmd.targetClasses, cmd.allowCapture, true, false); + } + }); + }, + "remove-guard": function(player, cmd, data) { for (let ent of data.entities)