1
0
forked from 0ad/0ad

Avoid errors when using planes in formations, by not allowing them to join formations

This was SVN commit r9776.
This commit is contained in:
Ykkrosh 2011-07-07 17:05:22 +00:00
parent 519b0020ba
commit b6d04004b6
6 changed files with 141 additions and 49 deletions

View File

@ -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 || "");

View File

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

View File

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

View File

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

View File

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

View File

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