From a85c6c69671c750910df0354fa1a404eedbe2ed5 Mon Sep 17 00:00:00 2001 From: elexis Date: Sun, 25 Sep 2016 21:33:05 +0000 Subject: [PATCH] Patrol units. Patch by Imarok, based on patch by svott, reviewed by fatherbushido and bb. This was SVN commit r18779. --- binaries/data/config/default.cfg | 1 + .../data/mods/public/gui/manual/intro.txt | 1 + .../data/mods/public/gui/session/input.js | 9 +- .../mods/public/gui/session/unit_actions.js | 65 ++++++++++ .../public/simulation/components/UnitAI.js | 121 ++++++++++++++++++ .../public/simulation/helpers/Commands.js | 7 + .../simulation/helpers/RallyPointCommands.js | 13 +- 7 files changed, 215 insertions(+), 2 deletions(-) diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 9bb055b37b..3a3e8996c4 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -287,6 +287,7 @@ attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when garrison = Ctrl ; Modifier to garrison when clicking on building autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building +patrol = "P" ; Modifier to patrol a unit repair = "J" ; Modifier to repair when clicking on building/mechanical unit queue = Shift ; Modifier to queue unit orders instead of replacing batchtrain = Shift ; Modifier to train units in batches diff --git a/binaries/data/mods/public/gui/manual/intro.txt b/binaries/data/mods/public/gui/manual/intro.txt index b850b81858..6697f38a82 100644 --- a/binaries/data/mods/public/gui/manual/intro.txt +++ b/binaries/data/mods/public/gui/manual/intro.txt @@ -85,6 +85,7 @@ Tab: See all status bars (which would also show the building progress) [font="sans-bold-14"]Modify mouse action [font="sans-14"]Ctrl + Right Click on building: Garrison J + Right Click on building: Repair +P + Right Click: Patrol Shift + Right Click: Queue the move/build/gather/etc order Shift + Left click when training unit/s: Add units in batches of five Shift + Left Click or Left Drag over unit on map: Add unit to selection diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index d4ea9c2962..4b1b0ecc68 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -16,6 +16,7 @@ const ACTION_NONE = 0; const ACTION_GARRISON = 1; const ACTION_REPAIR = 2; const ACTION_GUARD = 3; +const ACTION_PATROL = 4; var preSelectedAction = ACTION_NONE; const INPUT_NORMAL = 0; @@ -205,10 +206,16 @@ function getActionInfo(action, target) data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] }; cursor = "action-attack-move"; } + else if (Engine.HotkeyIsPressed("session.patrol")) + { + data.command = "patrol"; + data.targetClasses = { "attack": ["Unit"] }; + cursor = "action-patrol"; + } return { "possible": true, "data": data, "cursor": cursor }; } - return { "possible": (action == "move" || action == "attack-move" || action == "remove-guard") }; + return { "possible": ["move", "attack-move", "remove-guard", "patrol"].indexOf(action) > -1 }; } // Look at the first targeted entity diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js index 46b1ab1211..141c11e2d4 100644 --- a/binaries/data/mods/public/gui/session/unit_actions.js +++ b/binaries/data/mods/public/gui/session/unit_actions.js @@ -208,6 +208,52 @@ var unitActions = "specificness": 10, }, + "patrol": + { + "execute": function(target, action, selection, queued) + { + Engine.PostNetworkCommand({ + "type": "patrol", + "entities": selection, + "x": target.x, + "z": target.z, + "target": action.target, + "targetClasses": { "attack": ["Unit"] }, // patrol should only attack units + "queued": queued, + "allowCapture": false + }); + Engine.GuiInterfaceCall("PlaySound", { "name": "order_patrol", "entity": selection[0] }); + return true; + }, + "getActionInfo": function(entState, targetState) + { + return { "possible": true }; + }, + "hotkeyActionCheck": function(target, selection) + { + if (!someUnitAI(selection) || + !Engine.HotkeyIsPressed("session.patrol") || + !getActionInfo("patrol", target).possible) + return false; + return { + "type": "patrol", + "cursor": "action-patrol", + "target": target + }; + }, + "preSelectedActionCheck" : function(target) + { + if (preSelectedAction != ACTION_PATROL || !getActionInfo("patrol", target).possible) + return false; + return { + "type": "patrol", + "cursor": "action-patrol", + "target": target + }; + }, + "specificness": 37, + }, + "heal": { "execute": function(target, action, selection, queued) @@ -1219,6 +1265,25 @@ var g_EntityCommands = }, }, + "patrol": { + "getInfo": function(entState) + { + if (g_Selection.toList().every(ent => !GetEntityState(ent).unitAI)) + return false; + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.patrol") + + translate("Patrol") + "\n" + + translate("Attack encountered encountered units while avoiding buildings."), + "icon": "patrol.png" + }; + }, + "execute": function(entState) + { + inputState = INPUT_PRESELECTEDACTION; + preSelectedAction = ACTION_PATROL; + }, + }, + "share-dropsite": { "getInfo": function(entState) { diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index ebc63af60e..e23138fad5 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -514,6 +514,24 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); }, + "Order.Patrol": function(msg) { + // Let players move captured domestic animals around + if (this.IsAnimal() || this.IsTurret()) + { + this.FinishOrder(); + return; + } + + if (this.CanPack()) + { + this.PushOrderFront("Pack", { "force": true }); + return; + } + + this.MoveToPoint(this.order.data.x, this.order.data.z); + this.SetNextState("INDIVIDUAL.PATROL"); + }, + "Order.Heal": function(msg) { // Check the target is alive if (!this.TargetIsAlive(this.order.data.target)) @@ -829,6 +847,13 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); }, + "Order.Patrol": function(msg) { + this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + + this.MoveToPoint(this.order.data.x, this.order.data.z); + this.SetNextState("PATROL"); + }, + "Order.Guard": function(msg) { this.CallMemberFunction("Guard", [msg.data.target, false]); var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); @@ -1078,6 +1103,59 @@ UnitAI.prototype.UnitFsmSpec = { }, }, + "PATROL": { + "enter": function(msg) { + // Memorize the origin position in case that we want to go back + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld()) + { + this.FinishOrder(); + return; + } + if (!this.patrolStartPosOrder) + { + this.patrolStartPosOrder = cmpPosition.GetPosition(); + this.patrolStartPosOrder.targetClasses = { "attack": ["Unit"] }; + } + + this.StartTimer(0, 1000); + }, + + "Timer": function(msg) { + // Check if there are no enemies to attack + this.FindWalkAndFightTargets(); + }, + + "leave": function(msg) { + this.StopTimer(); + delete this.patrolStartPosOrder; + }, + + "MoveStarted": function(msg) { + let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + cmpFormation.SetRearrange(true); + cmpFormation.MoveMembersIntoFormation(true, true); + }, + + "MoveCompleted": function() { + /** + * A-B-A-B-..: + * if the user only commands one patrol order, the patrol will be between + * the last position and the defined waypoint + * A-B-C-..-A-B-..: + * otherwise, the patrol is only between the given patrol commands and the + * last position is not included (last position = the position where the unit + * is located at the time of the first patrol order) + */ + + if (this.orderQueue.length == 1) + this.PushOrder("Patrol", this.patrolStartPosOrder); + + this.PushOrder(this.order.type, this.order.data); + this.FinishOrder(); + }, + }, + "GARRISON":{ "enter": function() { // If the garrisonholder should pickup, warn it so it can take needed action @@ -1555,6 +1633,43 @@ UnitAI.prototype.UnitFsmSpec = { }, }, + "PATROL": { + "enter": function () { + // Memorize the origin position in case that we want to go back + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld()) + { + this.FinishOrder(); + return; + } + if (!this.patrolStartPosOrder) + { + this.patrolStartPosOrder = cmpPosition.GetPosition(); + this.patrolStartPosOrder.targetClasses = { "attack": ["Unit"] }; + } + + this.StartTimer(0, 1000); + this.SelectAnimation("move"); + }, + + "leave": function() { + this.StopTimer(); + delete this.patrolStartPosOrder; + }, + + "Timer": function(msg) { + this.FindWalkAndFightTargets(); + }, + + "MoveCompleted": function() { + if (this.orderQueue.length == 1) + this.PushOrder("Patrol",this.patrolStartPosOrder); + + this.PushOrder(this.order.type, this.order.data); + this.FinishOrder(); + }, + }, + "GUARD": { "RemoveGuard": function() { this.StopMoving(); @@ -4762,6 +4877,7 @@ UnitAI.prototype.GetTargetPositions = function() case "WalkToPointRange": case "MoveIntoFormation": case "GatherNearPosition": + case "Patrol": targetPositions.push(new Vector2D(order.data.x, order.data.z)); break; // and continue the loop @@ -4974,6 +5090,11 @@ UnitAI.prototype.WalkAndFight = function(x, z, targetClasses, queued) this.AddOrder("WalkAndFight", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued); }; +UnitAI.prototype.Patrol = function(x, z, targetClasses, queued) +{ + this.AddOrder("Patrol", { "x": x, "z": z, "targetClasses": targetClasses, "force": true }, queued); +}; + /** * Adds leave foundation order to queue, treated as forced. */ diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 9cea08481c..1715a19a59 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -176,6 +176,13 @@ var g_Commands = { }); }, + "patrol": function(player, cmd, data) + { + GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => + cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, cmd.queued) + ); + }, + "heal": function(player, cmd, data) { if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) diff --git a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js index e931b3a576..7b15d12bb7 100644 --- a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js +++ b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js @@ -32,7 +32,7 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts) switch (command) { case "gather": - ret.push( { + ret.push({ "type": "gather-near-position", "entities": spawnedEnts, "x": rallyPos[i].x, @@ -70,6 +70,17 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts) "queued": true }); break; + case "patrol": + ret.push( { + "type": "patrol", + "entities": spawnedEnts, + "x": rallyPos[i].x, + "z": rallyPos[i].z, + "target": data[i].target, + "targetClasses": data[i].targetClasses, + "queued": true + }); + break; case "attack": ret.push( { "type": "attack",