diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index c00cca1d91..783a9304c3 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -1090,11 +1090,14 @@ function performCommand(entity, commandName)
// Performs the specified formation
function performFormation(entity, formationName)
{
- submitChatDirectly("FORMATIONS are not implemented yet.");
-
if (entity)
{
- console.write(formationName);
+ var selection = g_Selection.toList();
+ Engine.PostNetworkCommand({
+ "type": "formation",
+ "entities": selection,
+ "name": formationName
+ });
}
}
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
index b39d2b73be..ad8b4ab454 100644
--- a/binaries/data/mods/public/gui/session/unit_commands.js
+++ b/binaries/data/mods/public/gui/session/unit_commands.js
@@ -248,9 +248,22 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
if (guiName == "Formation")
{
icon.cell_id = getFormationCellId(item);
- icon.enabled = false;
+ var formationOk = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", {
+ "ents": g_Selection.toList(),
+ "formationName": item
+ });
+
+ icon.enabled = formationOk;
+ button.enabled = formationOk;
if (!icon.enabled)
+ {
icon.sprite = "formation_disabled";
+ button.tooltip += " (disabled)";
+ }
+ else
+ {
+ icon.sprite = "formation";
+ }
}
else if (guiName == "Command")
{
@@ -399,4 +412,4 @@ function hideUnitCommands()
{
for each (var panelName in g_unitPanels)
getGUIObjectByName("unit" + panelName + "Panel").hidden = true;
-}
\ No newline at end of file
+}
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
index fefdab0fed..0bbd2adef0 100644
--- a/binaries/data/mods/public/gui/session/utility_functions.js
+++ b/binaries/data/mods/public/gui/session/utility_functions.js
@@ -223,29 +223,29 @@ function getFormationCellId(formationName)
{
switch (formationName)
{
- case "Formation0":
+ case "Loose":
return 0;
- case "Formation1":
+ case "Box":
return 1;
- case "Formation2":
+ case "Column Closed":
return 2;
- case "Formation3":
+ case "Line Closed":
return 3;
- case "Formation4":
+ case "Column Open":
return 4;
- case "Formation5":
+ case "Line Open":
return 5;
- case "Formation6":
+ case "Flank":
return 6;
- case "Formation7":
+ case "Skirmish":
return 7;
- case "Formation8":
+ case "Wedge":
return 8;
- case "Formation9":
+ case "Testudo":
return 9;
- case "Formation10":
- return 10;
- case "Formation11":
+ case "Phalanx":
+ return 10;
+ case "Syntagma":
return 11;
case "Formation12":
return 12;
@@ -273,24 +273,28 @@ function getCommandImage(commandName)
function getEntityFormationsList(entState)
{
- var formations = [];
-
- formations.push("Formation0");
- formations.push("Formation1");
- formations.push("Formation2");
- formations.push("Formation3");
- formations.push("Formation4");
- formations.push("Formation5");
- formations.push("Formation6");
- formations.push("Formation7");
- formations.push("Formation8");
- formations.push("Formation9");
- formations.push("Formation10");
- formations.push("Formation11");
- formations.push("Formation12");
+ var civ = g_Players[entState.player].civ;
+ var formations = getCivFormations(civ);
return formations;
}
+function getCivFormations(civ)
+{
+ // TODO: this should come from the civ JSON files instead
+
+ var civFormations = ["Loose", "Box", "Column Closed", "Line Closed", "Column Open", "Line Open", "Flank", "Skirmish", "Wedge", "Formation12"];
+ if (civ == "hele")
+ {
+ civFormations.push("Phalanx");
+ civFormations.push("Syntagma");
+ }
+ else if (civ == "rome")
+ {
+ civFormations.push("Testudo");
+ }
+ return civFormations;
+}
+
function getEntityCommandsList(entState)
{
var commands = [];
diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js
index 0dc4110822..956046e24b 100644
--- a/binaries/data/mods/public/simulation/components/Formation.js
+++ b/binaries/data/mods/public/simulation/components/Formation.js
@@ -3,12 +3,13 @@ function Formation() {}
Formation.prototype.Schema =
"";
-var g_ColumnDistanceThreshold = 48; // distance at which we'll switch between column/box formations
+var g_ColumnDistanceThreshold = 96; // distance at which we'll switch between column/box formations
Formation.prototype.Init = function()
{
this.members = []; // entity IDs currently belonging to this formation
this.columnar = false; // whether we're travelling in column (vs box) formation
+ this.formationName = "Line Closed";
};
Formation.prototype.GetMemberCount = function()
@@ -114,8 +115,6 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter)
var active = [];
var positions = [];
- var types = { "Unknown": 0 }; // TODO: make this work so we put ranged behind infantry etc
-
for each (var ent in this.members)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
@@ -124,16 +123,14 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter)
active.push(ent);
positions.push(cmpPosition.GetPosition());
-
- types["Unknown"] += 1; // TODO
}
// Work out whether this should be a column or box formation
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
var walkingDistance = cmpUnitAI.ComputeWalkingDistance();
- this.columnar = (walkingDistance > g_ColumnDistanceThreshold);
+ this.columnar = (walkingDistance > g_ColumnDistanceThreshold) && this.formationName != "Column Open";
- var offsets = this.ComputeFormationOffsets(types, this.columnar);
+ var offsets = this.ComputeFormationOffsets(active, this.columnar);
var avgpos = this.ComputeAveragePosition(positions);
var avgoffset = this.ComputeAveragePosition(offsets);
@@ -143,13 +140,11 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter)
if (moveCenter || !cmpPosition.IsInWorld())
cmpPosition.JumpTo(avgpos.x, avgpos.z);
- // TODO: assign to minimise worst-case distances or whatever
-
- for (var i = 0; i < active.length; ++i)
+ for (var i = 0; i < offsets.length; ++i)
{
var offset = offsets[i];
- var cmpUnitAI = Engine.QueryInterface(active[i], IID_UnitAI);
+ var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI);
cmpUnitAI.ReplaceOrder("FormationWalk", {
"target": this.entity,
"x": offset.x - avgoffset.x,
@@ -177,51 +172,369 @@ Formation.prototype.MoveToMembersCenter = function()
cmpPosition.JumpTo(avgpos.x, avgpos.z);
};
-Formation.prototype.ComputeFormationOffsets = function(types, columnar)
+Formation.prototype.ComputeFormationOffsets = function(active, columnar)
{
var separation = 4; // TODO: don't hardcode this
- var count = types["Unknown"];
+ var types = {
+ "Cavalry" : [],
+ "Hero" : [],
+ "Melee" : [],
+ "Ranged" : [],
+ "Support" : [],
+ "Unknown": []
+ }; // TODO: make this work so we put ranged behind infantry etc
- // Choose a sensible width for the basic default formation
+ for each (var ent in active)
+ {
+ var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+ var classes = cmpIdentity.GetClassesList();
+ var done = false;
+ for each (var cla in classes)
+ {
+ if (cla in types)
+ {
+ types[cla].push(ent);
+ done = true;
+ break;
+ }
+ }
+ if (!done)
+ {
+ types["Unknown"].push(ent);
+ }
+ }
+
+ var count = active.length;
+
+ var shape = undefined;
+ var ordering = "default";
+
+ var offsets = [];
+
+ // Choose a sensible size/shape for the various formations, depending on number of units
var cols;
- if (columnar)
+ if (columnar || this.formationName == "Column Closed")
{
// Have at most 3 files
if (count <= 3)
cols = count;
else
cols = 3;
+ shape = "square";
}
- else
+ else if (this.formationName == "Phalanx")
{
// Try to have at least 5 files (so batch training gives a single line),
// and at most 8
if (count <= 5)
cols = count;
- if (count <= 10)
+ else if (count <= 10)
cols = 5;
else if (count <= 16)
- cols = Math.ceil(count / 2);
- else
+ cols = Math.ceil(count/2);
+ else if (count <= 48)
cols = 8;
+ else
+ cols = Math.ceil(count/6);
+ shape = "square";
+ }
+ else if (this.formationName == "Line Closed")
+ {
+ if (count <= 3)
+ cols = count;
+ else if (count < 30)
+ cols = Math.max(Math.ceil(count/2), 3);
+ else
+ cols = Math.ceil(count/3);
+ shape = "square";
+ }
+ else if (this.formationName == "Testudo")
+ {
+ cols = Math.ceil(Math.sqrt(count));
+ shape = "square";
+ }
+ else if (this.formationName == "Column Open")
+ {
+ cols = 2
+ shape = "opensquare";
+ }
+ else if (this.formationName == "Line Open")
+ {
+ if (count <= 5)
+ cols = 3;
+ else if (count <= 11)
+ cols = 4;
+ else if (count <= 18)
+ cols = 5;
+ else
+ cols = 6;
+ shape = "opensquare";
+ }
+ else if (this.formationName == "Loose")
+ {
+ var width = Math.sqrt(count) * separation * 5;
+
+ for (var i = 0; i < count; ++i)
+ {
+ offsets.push({"x": Math.random()*width, "z": Math.random()*width});
+ }
+ }
+ else if (this.formationName == "Circle")
+ {
+ var depth;
+ var pop;
+ if (count <= 36)
+ {
+ pop = 12;
+ depth = Math.ceil(count / pop);
+ }
+ else
+ {
+ depth = 3
+ pop = Math.ceil(count / depth);
+ }
+
+ var left = count;
+ var radius = Math.min(left, pop) * separation / (2 * Math.PI);
+ for (var c = 0; c < depth; ++c)
+ {
+ var ctodo = Math.min(left, pop);
+ var cradius = radius - c * separation / 2;
+ var delta = 2 * Math.PI / ctodo;
+ for (var alpha = 0; ctodo; alpha += delta)
+ {
+ var x = Math.cos(alpha) * cradius;
+ var z = Math.sin(alpha) * cradius;
+ offsets.push({"x": x, "z": z});
+ ctodo--;
+ left--;
+ }
+ }
+ }
+ else if (this.formationName == "Box")
+ {
+ var root = Math.ceil(Math.sqrt(count));
+
+ var left = count;
+ var meleeleft = types["Melee"].length;
+ for (var sq = Math.floor(root/2); sq >= 0; --sq)
+ {
+ var width = sq * 2 + 1;
+ var stodo;
+ if (sq == 0)
+ {
+ stodo = left;
+ }
+ else
+ {
+ if (meleeleft >= width*width - (width-2)*(width-2)) // form a complete box
+ {
+ stodo = width*width - (width-2)*(width-2);
+ meleeleft -= stodo;
+ }
+ else // compact
+ stodo = Math.max(0, left - (width-2)*(width-2));
+ }
+
+ for (var r = -sq; r <= sq && stodo; ++r)
+ {
+ for (var c = -sq; c <= sq && stodo; ++c)
+ {
+ if (Math.abs(r) == sq || Math.abs(c) == sq)
+ {
+ var x = c * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ stodo--;
+ left--;
+ }
+ }
+ }
+ }
+ }
+ else if (this.formationName == "Skirmish")
+ {
+ cols = Math.ceil(count/2);
+ shape = "opensquare";
+ }
+ else if (this.formationName == "Wedge")
+ {
+ var depth = Math.ceil(Math.sqrt(count));
+
+ var left = count;
+ var width = 2 * depth - 1;
+ for (var p = 0; p < depth && left; ++p)
+ {
+ for (var r = p; r < depth && left; ++r)
+ {
+ var c1 = depth - r + p;
+ var c2 = depth + r - p;
+
+ if (left)
+ {
+ var x = c1 * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ left--;
+ }
+ if (left && c1 != c2)
+ {
+ var x = c2 * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ left--;
+ }
+ }
+ }
+ }
+ else if (this.formationName == "Flank")
+ {
+ cols = 3;
+ var leftside = [];
+ leftside[0] = Math.ceil(count/2);
+ leftside[1] = Math.floor(count/2);
+ ranks = Math.ceil(leftside[0] / cols);
+ var off = - separation * 4;
+ for (var side = 0; side < 2; ++side)
+ {
+ var left = leftside[side];
+ off += side * separation * 8;
+ for (var r = 0; r < ranks; ++r)
+ {
+ var n = Math.min(left, cols);
+ for (var c = 0; c < n; ++c)
+ {
+ var x = off + ((n-1)/2 - c) * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ }
+ left -= n;
+ }
+ }
+ }
+ else if (this.formationName == "Syntagma")
+ {
+ var cols = Math.ceil(Math.sqrt(count));
+ shape = "square";
+ }
+ else if (this.formationName == "Formation12")
+ {
+ if (count <= 5)
+ cols = count;
+ else if (count <= 10)
+ cols = 5;
+ else if (count <= 16)
+ cols = Math.ceil(count/2);
+ else if (count <= 48)
+ cols = 8;
+ else
+ cols = Math.ceil(count/6);
+ shape = "opensquare";
+ separation /= 1.5;
+ ordering = "cavalryOnTheSides";
}
- var ranks = Math.ceil(count / cols);
-
- var offsets = [];
-
- var left = count;
- for (var r = 0; r < ranks; ++r)
+ if (shape == "square")
{
- var n = Math.min(left, cols);
- for (var c = 0; c < n; ++c)
+ var ranks = Math.ceil(count / cols);
+
+ var left = count;
+ for (var r = 0; r < ranks; ++r)
{
- var x = ((n-1)/2 - c) * separation;
- var z = -r * separation;
- offsets.push({"x": x, "z": z});
+ var n = Math.min(left, cols);
+ for (var c = 0; c < n; ++c)
+ {
+ var x = ((n-1)/2 - c) * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ }
+ left -= n;
}
- left -= n;
+ }
+ else if (shape == "opensquare")
+ {
+ var left = count;
+ for (var r = 0; left; ++r)
+ {
+ var n = Math.min(left, cols - (r&1?1:0));
+ for (var c = 0; c < 2*n; c+=2)
+ {
+ var x = (- c - (r&1)) * separation;
+ var z = -r * separation;
+ offsets.push({"x": x, "z": z});
+ }
+ left -= n;
+ }
+ }
+
+ // TODO: assign to minimise worst-case distances or whatever
+ if (ordering == "default")
+ {
+ for each (var offset in offsets)
+ {
+ var ent = undefined;
+ for (var t in types)
+ {
+ if (types[t].length)
+ {
+ ent = types[t].pop();
+ offset.ent = ent;
+ break;
+ }
+ }
+ }
+ }
+ else if (ordering == "cavalryOnTheSides")
+ {
+ var noffset = [];
+ var cavalrycount = types["Cavalry"].length;
+ offsets.sort(function (a, b) {
+ if (a.x < b.x) return -1;
+ else if (a.x == b.x && a.z < b.z) return -1;
+ return 1;
+ });
+
+ while (offsets.length && types["Cavalry"].length && types["Cavalry"].length > cavalrycount/2)
+ {
+ var offset = offsets.pop();
+ offset.ent = types["Cavalry"].pop();
+ noffset.push(offset);
+ }
+
+ offsets.sort(function (a, b) {
+ if (a.x > b.x) return -1;
+ else if (a.x == b.x && a.z < b.z) return -1;
+ return 1;
+ });
+
+ while (offsets.length && types["Cavalry"].length)
+ {
+ var offset = offsets.pop();
+ offset.ent = types["Cavalry"].pop();
+ noffset.push(offset);
+ }
+
+ offsets.sort(function (a, b) {
+ if (a.z < b.z) return -1;
+ else if (a.z == b.z && a.x < b.x) return -1;
+ return 1;
+ });
+
+ while (offsets.length)
+ {
+ var offset = offsets.pop();
+ for (var t in types)
+ {
+ if (types[t].length)
+ {
+ offset.ent = types[t].pop();
+ break;
+ }
+ }
+ noffset.push(offset);
+ }
+ offsets = noffset;
}
return offsets;
@@ -286,4 +599,14 @@ Formation.prototype.OnGlobalOwnershipChanged = function(msg)
this.RemoveMembers([msg.entity]);
};
+Formation.prototype.LoadFormation = function(formationName)
+{
+ this.formationName = formationName;
+ for each (var ent in this.members)
+ {
+ var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ cmpUnitAI.SetLastFormationName(this.formationName);
+ }
+};
+
Engine.RegisterComponentType(IID_Formation, "Formation", Formation);
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 223c7bbccd..17f39d9123 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -264,6 +264,11 @@ GuiInterface.prototype.GetNextNotification = function()
return "";
};
+GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
+{
+ return CanMoveEntsIntoFormation(data.ents, data.formationName);
+};
+
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
@@ -510,6 +515,8 @@ var exposedFunctions = {
"GetTemplateData": 1,
"GetNextNotification": 1,
+ "CanMoveEntsIntoFormation": 1,
+
"SetSelectionHighlight": 1,
"SetStatusBars": 1,
"DisplayRallyPoint": 1,
diff --git a/binaries/data/mods/public/simulation/components/Identity.js b/binaries/data/mods/public/simulation/components/Identity.js
index a38ce5ff22..5b9303c677 100644
--- a/binaries/data/mods/public/simulation/components/Identity.js
+++ b/binaries/data/mods/public/simulation/components/Identity.js
@@ -68,6 +68,7 @@ Identity.prototype.Schema =
"Infantry" +
"Cavalry" +
"Ranged" +
+ "Melee" +
"Mechanical" +
"Ship" +
"Siege" +
diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js
index c0434a7513..cbc955eaad 100644
--- a/binaries/data/mods/public/simulation/components/UnitAI.js
+++ b/binaries/data/mods/public/simulation/components/UnitAI.js
@@ -1161,6 +1161,7 @@ UnitAI.prototype.Init = function()
this.order = undefined; // always == this.orderQueue[0]
this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to
this.isIdle = false;
+ this.lastFormationName = "Line Closed";
this.SetStance(this.template.DefaultStance);
};
@@ -1740,6 +1741,16 @@ UnitAI.prototype.GetFormationController = function()
return this.formationController;
};
+UnitAI.prototype.SetLastFormationName = function(name)
+{
+ this.lastFormationName = name;
+};
+
+UnitAI.prototype.GetLastFormationName = function()
+{
+ return this.lastFormationName;
+};
+
/**
* Returns the estimated distance that this unit will travel before either
* finishing all of its orders, or reaching a non-walk target (attack, gather, etc).
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index 2c15eb51cd..2337d1999e 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -237,6 +237,17 @@ function ProcessCommand(player, cmd)
var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
cmpGarrisonHolder.UnloadAll();
break;
+
+ case "formation":
+ var cmpUnitAI = GetFormationUnitAI(cmd.entities);
+ if (!cmpUnitAI)
+ break;
+ var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation);
+ if (!cmpFormation)
+ break;
+ cmpFormation.LoadFormation(cmd.name);
+ cmpFormation.MoveMembersIntoFormation(true);
+ break;
default:
error("Ignoring unrecognised command type '" + cmd.type + "'");
@@ -339,9 +350,139 @@ function GetFormationUnitAI(ents)
formationEnt = Engine.AddEntity("special/formation");
var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation);
cmpFormation.SetMembers(formation.entities);
+
+ // If all the selected units were previously in formations of the same shape,
+ // then set this new formation to that shape too; otherwise use the default shape
+ var lastFormationName = undefined;
+ for each (var ent in formation.entities)
+ {
+ var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (cmpUnitAI)
+ {
+ var name = cmpUnitAI.GetLastFormationName();
+ if (lastFormationName === undefined)
+ {
+ lastFormationName = name;
+ }
+ else if (lastFormationName != name)
+ {
+ lastFormationName = undefined;
+ break;
+ }
+ }
+ }
+ var formationName;
+ if (lastFormationName)
+ formationName = lastFormationName;
+ else
+ formationName = "Line Closed";
+
+ if (CanMoveEntsIntoFormation(formation.entities, formationName))
+ {
+ cmpFormation.LoadFormation(formationName);
+ }
+ else
+ {
+ cmpFormation.LoadFormation("Loose");
+ }
}
return Engine.QueryInterface(formationEnt, IID_UnitAI);
}
+function CanMoveEntsIntoFormation(ents, formationName)
+{
+ var count = ents.length;
+ var classesRequired;
+
+ // TODO: should check the player's civ is allowed to use this formation
+
+ if (formationName == "Loose")
+ {
+ return true;
+ }
+ else if (formationName == "Box")
+ {
+ if (count < 4)
+ return false;
+ }
+ else if (formationName == "Column Closed")
+ {
+ }
+ else if (formationName == "Line Closed")
+ {
+ }
+ else if (formationName == "Column Open")
+ {
+ }
+ else if (formationName == "Line Open")
+ {
+ }
+ else if (formationName == "Flank")
+ {
+ if (count < 8)
+ return false;
+ }
+ else if (formationName == "Skirmish")
+ {
+ classesRequired = ["Ranged"];
+ }
+ else if (formationName == "Wedge")
+ {
+ if (count < 3)
+ return false;
+ classesRequired = ["Cavalry"];
+ }
+ else if (formationName == "Formation12")
+ {
+ }
+ else if (formationName == "Phalanx")
+ {
+ if (count < 10)
+ return false;
+ classesRequired = ["Melee", "Infantry"];
+ }
+ else if (formationName == "Syntagma")
+ {
+ if (count < 9)
+ return false;
+ classesRequired = ["Melee", "Infantry"]; // TODO: pike only
+ }
+ else if (formationName == "Testudo")
+ {
+ if (count < 9)
+ return false;
+ classesRequired = ["Melee", "Infantry"];
+ }
+ else
+ {
+ return false;
+ }
+
+ var looseOnlyUnits = true;
+ for each (var ent in ents)
+ {
+ var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+ if (cmpIdentity)
+ {
+ var classes = cmpIdentity.GetClassesList();
+ if (looseOnlyUnits && (classes.indexOf("Worker") == -1 || classes.indexOf("Support") == -1))
+ looseOnlyUnits = false;
+ for each (var classRequired in classesRequired)
+ {
+ if (classes.indexOf(classRequired) == -1)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (looseOnlyUnits)
+ return false;
+
+ return true;
+}
+
+Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
Engine.RegisterGlobal("ProcessCommand", ProcessCommand);
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
index 4fa67b571f..523af758a2 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml
@@ -2,7 +2,7 @@
Hero
- Hero Infantry
+ Hero Infantry Melee
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
index d898a02e79..a924221d3c 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml
@@ -2,7 +2,7 @@
Hero
- Hero Infantry
+ Hero Infantry Melee
hero
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
index 069351dc2b..86eff15b66 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml
@@ -2,7 +2,7 @@
Hero
- Hero Infantry
+ Hero Infantry Melee
400
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
index 63ea4bb9d1..791619cb10 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml
@@ -2,6 +2,7 @@
Melee Infantry
+ Melee
100
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
index 8f8831e550..c73f81c209 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
@@ -2,6 +2,7 @@
Ranged
+ Ranged
90
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml
index 5811c5d4a9..14b07e2912 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml
@@ -2,7 +2,7 @@
Super Cavalry Javelinist
- Cavalry Ranged
+ Ranged
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml
index cedd9bf8c6..9f5923bab8 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml
@@ -2,6 +2,7 @@
Super Cavalry Spearman
+ Melee
16
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml
index 3a660f5f95..843ce30676 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml
@@ -2,6 +2,7 @@
Super Cavalry Spearman
+ Melee
16
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml
index 4fc85da100..e6a78de35c 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml
@@ -2,6 +2,7 @@
Super Infantry Pikeman
+ Melee
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml
index 124b98a62a..14bda3b643 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml
@@ -2,6 +2,7 @@
Super Infantry Spearman
+ Melee
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml
index 525d677a84..cb8b1985e1 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml
@@ -2,6 +2,7 @@
Super Infantry Swordsman
+ Melee