diff --git a/binaries/data/mods/public/simulation/components/Identity.js b/binaries/data/mods/public/simulation/components/Identity.js
index 179acb7f0e..246088396b 100644
--- a/binaries/data/mods/public/simulation/components/Identity.js
+++ b/binaries/data/mods/public/simulation/components/Identity.js
@@ -98,6 +98,32 @@ Identity.prototype.Schema =
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "Loose" +
+ "Box" +
+ "ColumnClosed" +
+ "LineClosed" +
+ "ColumnOpen" +
+ "LineOpen" +
+ "Flank" +
+ "Skirmish" +
+ "Wedge" +
+ "Testudo" +
+ "Phalanx" +
+ "Syntagma" +
+ "Formation12" +
+ "" +
+ "" +
+ "
" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -123,7 +149,7 @@ Identity.prototype.GetRank = function()
Identity.prototype.GetClassesList = function()
{
- if (this.template.Classes)
+ if (this.template.Classes && "_string" in this.template.Classes)
{
var string = this.template.Classes._string;
return string.split(/\s+/);
@@ -139,6 +165,24 @@ Identity.prototype.HasClass = function(name)
return this.GetClassesList().indexOf(name) != -1;
};
+Identity.prototype.GetFormationsList = function()
+{
+ if (this.template.Formations && "_string" in this.template.Formations)
+ {
+ var string = this.template.Formations._string;
+ return string.split(/\s+/);
+ }
+ else
+ {
+ return [];
+ }
+};
+
+Identity.prototype.CanUseFormation = function(name)
+{
+ return this.GetFormationsList().indexOf(name) != -1;
+};
+
Identity.prototype.GetSelectionGroupName = function()
{
return (this.template.SelectionGroupName || "");
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index cf6c7d7524..2b50d3efc3 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -37,9 +37,9 @@ function ProcessCommand(player, cmd)
case "walk":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
+ });
break;
case "attack":
@@ -47,9 +47,9 @@ function ProcessCommand(player, cmd)
if (IsOwnedByEnemyOfPlayer(player, cmd.target))
{
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Attack(cmd.target, cmd.queued);
+ });
}
break;
@@ -59,17 +59,17 @@ function ProcessCommand(player, cmd)
if (IsOwnedByAllyOfPlayer(player, cmd.target))
{
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued);
+ });
}
break;
case "gather":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Gather(cmd.target, cmd.queued);
+ });
break;
case "returnresource":
@@ -77,9 +77,9 @@ function ProcessCommand(player, cmd)
if (IsOwnedByPlayer(cmd.target, player))
{
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.ReturnResource(cmd.target, cmd.queued);
+ });
}
break;
@@ -247,9 +247,9 @@ function ProcessCommand(player, cmd)
if (CanControlUnit(cmd.target, player, controlAllUnits))
{
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (cmpUnitAI)
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
cmpUnitAI.Garrison(cmd.target);
+ });
}
break;
@@ -272,14 +272,13 @@ function ProcessCommand(player, cmd)
case "formation":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
- var cmpUnitAI = GetFormationUnitAI(entities);
- if (!cmpUnitAI)
- break;
- var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation);
- if (!cmpFormation)
- break;
- cmpFormation.LoadFormation(cmd.name);
- cmpFormation.MoveMembersIntoFormation(true);
+ GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
+ var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation);
+ if (!cmpFormation)
+ return;
+ cmpFormation.LoadFormation(cmd.name);
+ cmpFormation.MoveMembersIntoFormation(true);
+ });
break;
case "promote":
@@ -311,6 +310,7 @@ function ProcessCommand(player, cmd)
/**
* Get some information about the formations used by entities.
+ * The entities must have a UnitAI component.
*/
function ExtractFormations(ents)
{
@@ -319,17 +319,14 @@ function ExtractFormations(ents)
for each (var ent in ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
- if (cmpUnitAI)
+ var fid = cmpUnitAI.GetFormationController();
+ if (fid != INVALID_ENTITY)
{
- var fid = cmpUnitAI.GetFormationController();
- if (fid != INVALID_ENTITY)
- {
- if (!members[fid])
- members[fid] = [];
- members[fid].push(ent);
- }
- entities.push(ent);
+ if (!members[fid])
+ members[fid] = [];
+ members[fid].push(ent);
}
+ entities.push(ent);
}
var ids = [ id for (id in members) ];
@@ -352,29 +349,57 @@ function RemoveFromFormation(ents)
}
/**
- * Return null or a UnitAI belonging either to the selected unit
- * or to a formation entity for the selected group of units.
+ * Returns a list of UnitAI components, each belonging either to a
+ * selected unit or to a formation entity for groups of the selected units.
*/
-function GetFormationUnitAI(ents)
+function GetFormationUnitAIs(ents)
{
// If an individual was selected, remove it from any formation
// and command it individually
if (ents.length == 1)
{
+ // Skip unit if it has no UnitAI
+ var cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI);
+ if (!cmpUnitAI)
+ return [];
+
RemoveFromFormation(ents);
- return Engine.QueryInterface(ents[0], IID_UnitAI);
+ return [ cmpUnitAI ];
}
-
- // Find what formations the selected entities are currently in
- var formation = ExtractFormations(ents);
- if (formation.entities.length == 0)
+ // Separate out the units that don't support the chosen formation
+ var formedEnts = [];
+ var nonformedUnitAIs = [];
+ for each (var ent in ents)
{
- // No units with AI - nothing to do here
- return null;
+ // Skip units with no UnitAI
+ var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (!cmpUnitAI)
+ continue;
+
+ var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+ // TODO: Currently we use LineClosed as effectively a boolean flag
+ // to determine whether formations are allowed at all. Instead we
+ // should check specific formation names and do something sensible
+ // (like what?) when some units don't support them.
+ // TODO: We'll also need to fix other formation code to use
+ // "LineClosed" instead of "Line Closed" etc consistently.
+ if (cmpIdentity && cmpIdentity.CanUseFormation("LineClosed"))
+ formedEnts.push(ent);
+ else
+ nonformedUnitAIs.push(cmpUnitAI);
}
+ if (formedEnts.length == 0)
+ {
+ // No units support the foundation - return all the others
+ return nonformedUnitAIs;
+ }
+
+ // Find what formations the formationable selected entities are currently in
+ var formation = ExtractFormations(formedEnts);
+
var formationEnt = undefined;
if (formation.ids.length == 1)
{
@@ -442,7 +467,7 @@ function GetFormationUnitAI(ents)
}
}
- return Engine.QueryInterface(formationEnt, IID_UnitAI);
+ return nonformedUnitAIs.concat(Engine.QueryInterface(formationEnt, IID_UnitAI));
}
function CanMoveEntsIntoFormation(ents, formationName)
diff --git a/binaries/data/mods/public/simulation/templates/other/plane.xml b/binaries/data/mods/public/simulation/templates/other/plane.xml
index 5aa67e3659..2548adb820 100644
--- a/binaries/data/mods/public/simulation/templates/other/plane.xml
+++ b/binaries/data/mods/public/simulation/templates/other/plane.xml
@@ -4,11 +4,12 @@
hele
P-51 Mustang
This may be anachronistic.
- A World War 2 American fighter plane.
+ A World War 2 American fighter plane.
units/global_mustang.png
+
- units/global/plane.xml
+ units/global/plane.xml
100
diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml
index 911474fa83..dcffa04995 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -3,6 +3,21 @@
Unit
Unit ConquestCritical
+
+ Loose
+ Box
+ ColumnClosed
+ LineClosed
+ ColumnOpen
+ LineOpen
+ Flank
+ Skirmish
+ Wedge
+ Testudo
+ Phalanx
+ Syntagma
+ Formation12
+
unit
diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp
index 037428ea74..9c1cde3224 100644
--- a/source/simulation2/system/ParamNode.cpp
+++ b/source/simulation2/system/ParamNode.cpp
@@ -83,6 +83,7 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element)
int at_disable = xmb.GetAttributeID("disable");
int at_replace = xmb.GetAttributeID("replace");
int at_datatype = xmb.GetAttributeID("datatype");
+ bool replacing = false;
{
XERO_ITER_ATTR(element, attr)
{
@@ -94,16 +95,22 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element)
else if (attr.Name == at_replace)
{
m_Childs.erase(name);
- break;
+ replacing = true;
}
- else if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens")
+ }
+ }
+ {
+ XERO_ITER_ATTR(element, attr)
+ {
+ if (attr.Name == at_datatype && std::wstring(attr.Value.begin(), attr.Value.end()) == L"tokens")
{
CParamNode& node = m_Childs[name];
// Split into tokens
std::vector oldTokens;
std::vector newTokens;
- boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space());
+ if (!replacing) // ignore the old tokens if replace="" was given
+ boost::algorithm::split(oldTokens, node.m_Value, boost::algorithm::is_space());
boost::algorithm::split(newTokens, value, boost::algorithm::is_space());
// Delete empty tokens
diff --git a/source/simulation2/tests/test_ParamNode.h b/source/simulation2/tests/test_ParamNode.h
index 223da2b24d..2cb33caa0e 100644
--- a/source/simulation2/tests/test_ParamNode.h
+++ b/source/simulation2/tests/test_ParamNode.h
@@ -112,9 +112,9 @@ public:
void test_overlay_tokens()
{
CParamNode node;
- TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " x ya b\nc\td"), PSRETURN_OK);
- TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " -y z w"), PSRETURN_OK);
- TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"x z wa b c d");
+ TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " x ya b\nc\tdm n"), PSRETURN_OK);
+ TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, " -y z wn o"), PSRETURN_OK);
+ TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"x z wa b c dn o");
}
void test_types()