From 18ee932a01e0ab01e53d18a5f8be888fb2315cef Mon Sep 17 00:00:00 2001 From: quantumstate Date: Mon, 25 Feb 2013 21:56:24 +0000 Subject: [PATCH] Adds attack move bound to the Ctrl hotkey. Patch from mimo. Refs #1001. This was SVN commit r13200. --- binaries/data/config/default.cfg | 3 +- .../data/mods/public/gui/manual/intro.txt | 3 + .../data/mods/public/gui/session/input.js | 26 ++- .../public/simulation/components/UnitAI.js | 149 +++++++++++++++++- .../public/simulation/helpers/Commands.js | 7 + 5 files changed, 179 insertions(+), 9 deletions(-) diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 7aa2a45d1d..7d08d7eebf 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -243,8 +243,9 @@ hotkey.selection.group.add.9 = "Shift+9" ; > SESSION CONTROLS hotkey.session.kill = Delete ; Destroy selected units -hotkey.session.attack = Ctrl ; Modifier to force attack instead of another action +hotkey.session.attack = AltGr ; Modifier to force attack instead of another action hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building +hotkey.session.attackmove = Ctrl ; Modifier to attackmove when clicking on a point hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources diff --git a/binaries/data/mods/public/gui/manual/intro.txt b/binaries/data/mods/public/gui/manual/intro.txt index 75bfde2cc3..fda80121cf 100644 --- a/binaries/data/mods/public/gui/manual/intro.txt +++ b/binaries/data/mods/public/gui/manual/intro.txt @@ -76,6 +76,9 @@ Ctrl + Left Click or Left Drag over unit on map: Remove unit from selection Alt + Left Drag over units on map: Only select military units Ctrl + Left Click on unit/group icon with multiple units selected: Deselect Right Click with a building/buildings selected: sets a rally point for units created/ungarrisoned from that building. +Ctrl + Right Click with units selected: + - If the cursor is over a structure: Garrison + - Otherwise: Attack move [font="serif-bold-14"]Overlays [font="serif-14"]Alt + G: Hide/show the GUI diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index e4ae2c804d..6cc2d26c5c 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -187,7 +187,7 @@ function getActionInfo(action, target) { if (action == "set-rallypoint" && haveRallyPoints) return {"possible": true}; - else if (action == "move") + else if (action == "move" || action == "attack-move") return {"possible": true}; else return {"possible": false}; @@ -404,7 +404,7 @@ function getActionInfo(action, target) break; } } - if (action == "move") + if (action == "move" || action == "attack-move") return {"possible": true}; else return {"possible": false}; @@ -480,12 +480,13 @@ function determineAction(x, y, fromMinimap) { return {"type": "attack", "cursor": "action-attack", "target": target}; } - else if (Engine.HotkeyIsPressed("session.garrison")) + else if (Engine.HotkeyIsPressed("session.garrison") && getActionInfo("garrison", target).possible) { - if (getActionInfo("garrison", target).possible) - return {"type": "garrison", "cursor": "action-garrison", "target": target}; - else - return {"type": "none", "cursor": "action-garrison-disabled", "target": undefined}; + return {"type": "garrison", "cursor": "action-garrison", "target": target}; + } + else if (Engine.HotkeyIsPressed("session.attackmove") && getActionInfo("attack-move", target).possible) + { + return {"type": "attack-move", "cursor": "action-attack"}; } else { @@ -1320,6 +1321,12 @@ function doAction(action, ev) Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); return true; + case "attack-move": + var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); + Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); + Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); + return true; + case "attack": Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued}); Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); @@ -1419,6 +1426,11 @@ function handleMinimapEvent(target) Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); return true; + case "attack-move": + Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued}); + Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] }); + return true; + case "set-rallypoint": Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z}); // Display rally point at the new coordinates, to avoid display lag diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 916a4add75..48f0ad935f 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -270,6 +270,33 @@ var UnitFsmSpec = { this.SetNextState("INDIVIDUAL.WALKING"); }, + "Order.WalkAndFight": function(msg) { + // Let players move captured domestic animals around + if (this.IsAnimal() && !this.IsDomestic()) + { + this.FinishOrder(); + return; + } + + // For packable units: + // 1. If packed, we can move. + // 2. If unpacked, we first need to pack, then follow case 1. + if (this.CanPack()) + { + // Case 2: pack + this.PushOrderFront("Pack", { "force": true }); + return; + } + + this.SetHeldPosition(this.order.data.x, this.order.data.z); + this.MoveToPoint(this.order.data.x, this.order.data.z); + if (this.IsAnimal()) + this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals + else + this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING"); + }, + + "Order.WalkToTarget": function(msg) { // Let players move captured domestic animals around if (this.IsAnimal() && !this.IsDomestic()) @@ -353,8 +380,10 @@ var UnitFsmSpec = { if (this.CanUnpack()) { // Ignore unforced attacks + // this would prevent attacks from AttackVisibleEntity or AttackEntityInZone ? + // so we accept attacks against targets for which we have a bonus // TODO: use special stances instead? - if (!this.order.data.force) + if (!this.order.data.force && this.GetAttackBonus(type, this.order.data.target) < 1.5) { this.FinishOrder(); return; @@ -628,6 +657,14 @@ var UnitFsmSpec = { this.MoveToPoint(this.order.data.x, this.order.data.z); this.SetNextState("WALKING"); }, + + "Order.WalkAndFight": function(msg) { + var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + cmpFormation.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + + this.MoveToPoint(this.order.data.x, this.order.data.z); + this.SetNextState("WALKINGANDFIGHTING"); + }, "Order.MoveIntoFormation": function(msg) { var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); @@ -885,6 +922,54 @@ var UnitFsmSpec = { }, }, + "WALKINGANDFIGHTING": { + "enter": function(msg) { + this.StartTimer(0, 1000); + }, + + "Timer": function(msg) { + // check if there are no enemies to attack + var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + for each (var ent in cmpFormation.members) + { + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI.FindNewTargets()) + { + if (cmpUnitAI.orderQueue[0] && cmpUnitAI.orderQueue[0].type == "Attack") + { + var data = cmpUnitAI.orderQueue[0].data; + cmpUnitAI.FinishOrder(); + this.PushOrderFront("Attack", { "target": data.target, "force": false, "forceResponse": data.forceResponse }); + break; + } + } + } + }, + + "leave": function(msg) { + this.StopTimer(); + }, + + "MoveStarted": function(msg) { + var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + cmpFormation.SetRearrange(true); + cmpFormation.MoveMembersIntoFormation(true, true); + }, + + "MoveCompleted": function(msg) { + var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + + if (this.FinishOrder()) + { + cmpFormation.CallMemberFunction("ResetFinishOrder", []); + return; + } + + // No more orders left. + cmpFormation.Disband(); + }, + }, + "FORMING": { "MoveStarted": function(msg) { var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); @@ -938,7 +1023,27 @@ var UnitFsmSpec = { // Execute the next order if (this.FinishOrder()) + { + // if WalkAndFight order, look for new target before moving again + if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight") + { + for each (var ent in cmpFormation.members) + { + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI.FindNewTargets()) + { + if (cmpUnitAI.orderQueue[0] && cmpUnitAI.orderQueue[0].type == "Attack") + { + var data = cmpUnitAI.orderQueue[0].data; + cmpUnitAI.FinishOrder(); + this.PushOrderFront("Attack", { "target": data.target, "force": false, "forceResponse": data.forceResponse }); + break; + } + } + } + } return; + } // No more order left. cmpFormation.Disband(); @@ -1146,6 +1251,25 @@ var UnitFsmSpec = { }, }, + "WALKINGANDFIGHTING": { + "enter": function () { + this.StartTimer(0, 1000); + this.SelectAnimation("move"); + }, + + "Timer": function(msg) { + this.FindNewTargets(); + }, + + "leave": function(msg) { + this.StopTimer(); + }, + + "MoveCompleted": function() { + this.FinishOrder(); + }, + }, + "FLEEING": { "enter": function() { this.PlaySound("panic"); @@ -1335,8 +1459,13 @@ var UnitFsmSpec = { } // Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up + // Except if in WalkAndFight mode where we look for more ennemies around before moving again if (this.FinishOrder()) + { + if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight") + this.FindNewTargets(); return; + } // See if we can switch to a new nearby enemy if (this.FindNewTargets()) @@ -3213,6 +3342,14 @@ UnitAI.prototype.GetBestAttackAgainst = function(target) return cmpAttack.GetBestAttackAgainst(target); }; +UnitAI.prototype.GetAttackBonus = function(type, target) +{ + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return 1; + return cmpAttack.GetAttackBonus(type, target); +}; + /** * Try to find one of the given entities which can be attacked, * and start attacking it. @@ -3425,6 +3562,7 @@ UnitAI.prototype.ComputeWalkingDistance = function() switch (order.type) { case "Walk": + case "WalkAndFight": case "WalkToPointRange": case "MoveIntoFormation": case "GatherNearPosition": @@ -3510,6 +3648,15 @@ UnitAI.prototype.WalkToTarget = function(target, queued) this.AddOrder("WalkToTarget", { "target": target, "force": true }, queued); }; +/** + * Adds walk-and-fight order to queue, this only occurs in response + * to a player order, and so is forced. + */ +UnitAI.prototype.WalkAndFight = function(x, z, queued) +{ + this.AddOrder("WalkAndFight", { "x": x, "z": z, "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 513dc5cc98..89b85d64c2 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -88,6 +88,13 @@ function ProcessCommand(player, cmd) }); break; + case "attack-walk": + var entities = FilterEntityList(cmd.entities, player, controlAllUnits); + GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) { + cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.queued); + }); + break; + case "attack": if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) {