Avoid errors when using planes in formations, by not allowing them to join formations
This was SVN commit r9776.
This commit is contained in:
parent
519b0020ba
commit
b6d04004b6
@ -98,6 +98,32 @@ Identity.prototype.Schema =
|
||||
"</list>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Formations'>" +
|
||||
"<attribute name='datatype'>" +
|
||||
"<value>tokens</value>" +
|
||||
"</attribute>" +
|
||||
"<list>" +
|
||||
"<zeroOrMore>" +
|
||||
"<choice>" +
|
||||
"<value>Loose</value>" +
|
||||
"<value>Box</value>" +
|
||||
"<value>ColumnClosed</value>" +
|
||||
"<value>LineClosed</value>" +
|
||||
"<value>ColumnOpen</value>" +
|
||||
"<value>LineOpen</value>" +
|
||||
"<value>Flank</value>" +
|
||||
"<value>Skirmish</value>" +
|
||||
"<value>Wedge</value>" +
|
||||
"<value>Testudo</value>" +
|
||||
"<value>Phalanx</value>" +
|
||||
"<value>Syntagma</value>" +
|
||||
"<value>Formation12</value>" +
|
||||
"</choice>" +
|
||||
"</zeroOrMore>" +
|
||||
"</list>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Icon'>" +
|
||||
"<text/>" +
|
||||
@ -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 || "");
|
||||
|
@ -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)
|
||||
|
@ -4,11 +4,12 @@
|
||||
<Civ>hele</Civ>
|
||||
<SpecificName>P-51 Mustang</SpecificName>
|
||||
<History>This may be anachronistic.</History>
|
||||
<Tooltip>A World War 2 American fighter plane.</Tooltip>
|
||||
<Tooltip>A World War 2 American fighter plane.</Tooltip>
|
||||
<Icon>units/global_mustang.png</Icon>
|
||||
<Formations datatype="tokens" replace=""></Formations>
|
||||
</Identity>
|
||||
<VisualActor>
|
||||
<Actor>units/global/plane.xml</Actor> <!-- only using this because its flaming projectiles look nice -->
|
||||
<Actor>units/global/plane.xml</Actor>
|
||||
</VisualActor>
|
||||
<Vision>
|
||||
<Range>100</Range>
|
||||
|
@ -3,6 +3,21 @@
|
||||
<Identity>
|
||||
<GenericName>Unit</GenericName>
|
||||
<Classes datatype="tokens">Unit ConquestCritical</Classes>
|
||||
<Formations datatype="tokens">
|
||||
Loose
|
||||
Box
|
||||
ColumnClosed
|
||||
LineClosed
|
||||
ColumnOpen
|
||||
LineOpen
|
||||
Flank
|
||||
Skirmish
|
||||
Wedge
|
||||
Testudo
|
||||
Phalanx
|
||||
Syntagma
|
||||
Formation12
|
||||
</Formations>
|
||||
</Identity>
|
||||
<Minimap>
|
||||
<Type>unit</Type>
|
||||
|
@ -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<std::wstring> oldTokens;
|
||||
std::vector<std::wstring> 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
|
||||
|
@ -112,9 +112,9 @@ public:
|
||||
void test_overlay_tokens()
|
||||
{
|
||||
CParamNode node;
|
||||
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a datatype='tokens'>x y</a><b datatype='tokens'>a b\nc\td</b></test>"), PSRETURN_OK);
|
||||
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a datatype='tokens'>-y z w</a></test>"), PSRETURN_OK);
|
||||
TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a datatype=\"tokens\">x z w</a><b datatype=\"tokens\">a b c d</b></test>");
|
||||
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a datatype='tokens'>x y</a><b datatype='tokens'>a b\nc\td</b><c datatype='tokens'>m n</c></test>"), PSRETURN_OK);
|
||||
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(node, "<test> <a datatype='tokens'>-y z w</a><c datatype='tokens' replace=''>n o</c></test>"), PSRETURN_OK);
|
||||
TS_ASSERT_WSTR_EQUALS(node.ToXML(), L"<test><a datatype=\"tokens\">x z w</a><b datatype=\"tokens\">a b c d</b><c datatype=\"tokens\">n o</c></test>");
|
||||
}
|
||||
|
||||
void test_types()
|
||||
|
Loading…
Reference in New Issue
Block a user