1
0
forked from 0ad/0ad

Adds attack move bound to the Ctrl hotkey. Patch from mimo. Refs #1001.

This was SVN commit r13200.
This commit is contained in:
Jonathan Waller 2013-02-25 21:56:24 +00:00
parent ac32c7266e
commit 18ee932a01
5 changed files with 179 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
*/

View File

@ -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)))
{