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()