1
0
forked from 0ad/0ad

Remove some hardcoded formation shapes. Some special formations still have a special treatment though. Also implement speed modifiers for formations, so we can have faster and slower formations.

This was SVN commit r14516.
This commit is contained in:
sanderd17 2014-01-05 17:13:22 +00:00
parent aa0d485d72
commit b8ee766cd3
13 changed files with 143 additions and 117 deletions

View File

@ -3,12 +3,56 @@ function Formation() {}
Formation.prototype.Schema =
"<element name='FormationName'>" +
"<text/>" +
"</element>" +
"<element name='SpeedMultiplier'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='FormationShape'>" +
"<text/>" +
"</element>" +
"<element name='ShiftRows'>" +
"<text/>" +
"</element>" +
"<element name='WidthDepthRatio'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<optional>" +
"<element name='MinColumns'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='MaxColumns'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='MaxRows'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<element name='UnitSeparationWidthMultiplier'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='UnitSeparationDepthMultiplier'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>";
var g_ColumnDistanceThreshold = 128; // distance at which we'll switch between column/box formations
Formation.prototype.Init = function()
{
this.formationShape = this.template.FormationShape;
this.shiftRows = this.template.ShiftRows == "true";
this.separationMultiplier = {
"width": +this.template.UnitSeparationWidthMultiplier,
"depth": +this.template.UnitSeparationDepthMultiplier
};
this.widthDepthRatio = +this.template.WidthDepthRatio;
this.minColumns = +(this.template.MinColumns || 0);
this.maxColumns = +(this.template.MaxColumns || 0);
this.maxRows = +(this.template.MaxRows || 0);
this.members = []; // entity IDs currently belonging to this formation
this.inPosition = []; // entities that have reached their final position
this.columnar = false; // whether we're travelling in column (vs box) formation
@ -38,6 +82,11 @@ Formation.prototype.GetSize = function()
return {"width": this.width, "depth": this.depth};
};
Formation.prototype.GetSpeedMultiplier = function()
{
return +this.template.SpeedMultiplier;
};
Formation.prototype.GetMemberCount = function()
{
return this.members.length;
@ -456,6 +505,8 @@ Formation.prototype.GetAvgFootprint = function(active)
Formation.prototype.ComputeFormationOffsets = function(active, positions, columnar)
{
var separation = this.GetAvgFootprint(active);
separation.width *= this.separationMultiplier.width;
separation.depth *= this.separationMultiplier.depth;
// the entities will be assigned to positions in the formation in
// the same order as the types list is ordered
@ -483,14 +534,12 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions, column
}
}
if (!done)
{
types["Unknown"].push({"ent": active[i], "pos": positions[i]});
}
}
var count = active.length;
var shape = undefined;
var shape = this.formationShape;
var ordering = [];
var offsets = [];
@ -499,73 +548,30 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions, column
var cols;
if (columnar)
var formationName = "Column Closed";
else
var formationName = this.formationName;
switch(formationName)
{
case "Column Closed":
// Have at most 3 files
if (count <= 3)
cols = count;
else
cols = 3;
shape = "square";
break;
case "Phalanx":
// Try to have at least 5 files (so batch training gives a single line),
// and at most 8
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);
separation.width *= 0.7;
shape = "square";
break;
case "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";
ordering = ["FillFromTheCenter", "FillFromTheFront"];
break;
case "Testudo":
cols = Math.ceil(Math.sqrt(count));
shape = "square";
break;
case "Column Open":
cols = 2;
shape = "opensquare";
break;
case "Line Open":
if (count <= 5)
cols = 3;
else if (count <= 11)
cols = 4;
else if (count <= 18)
cols = 5;
else
cols = 6;
shape = "opensquare";
ordering = ["FillFromTheCenter", "FillFromTheFront"];
break;
cols = Math.min(count,3);
}
else
{
var depth = Math.sqrt(count / this.widthDepthRatio);
if (this.maxRows && depth > this.maxRows)
depth = this.maxRows;
cols = Math.ceil(count / Math.ceil(depth) + (this.shiftRows ? 0.5 : 0));
if (cols < this.minColumns)
cols = Math.min(count, this.minColumns);
if (this.maxColumns && cols > this.maxColumns && this.maxRows != depth)
cols = this.maxColumns;
}
// define special formations here
switch(this.formationName)
{
case "Scatter":
var width = Math.sqrt(count) * (separation.width + separation.depth) * 2.5;
for (var i = 0; i < count; ++i)
{
offsets.push({"x": Math.random()*width, "z": Math.random()*width});
}
break;
case "Box":
var root = Math.ceil(Math.sqrt(count));
@ -609,10 +615,6 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions, column
}
}
break;
case "Skirmish":
cols = Math.ceil(count/2);
shape = "opensquare";
break;
case "Wedge":
var depth = Math.ceil(Math.sqrt(count));
@ -667,63 +669,40 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions, column
}
ordering.push("FillFromTheCenter");
break;
case "Syntagma":
cols = Math.ceil(Math.sqrt(count));
shape = "square";
break;
case "Battle Line":
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.width /= 2;
separation.depth /= 1.5;
ordering.push("FillFromTheSides");
break;
default:
warn("Unknown formation: " + formationName);
break;
}
if (shape == "square")
{
var ranks = Math.ceil(count / cols);
var r = 0;
var left = count;
while (left > 0)
{
var n = cols;
var sign = 1;
if (this.shiftRows)
n -= r%2;
else if (n > left)
n = left;
for (var c = 0; c < n && left > 0; ++c)
{
sign *= -1;
if (n%2 == 0)
var x = sign * (Math.floor(c/2) + 0.5) * separation.width;
else
var x = sign * Math.ceil(c/2) * separation.width;
var z = -r * separation.depth;
offsets.push({"x": x, "z": z});
left--
}
++r;
}
}
var left = count;
for (var r = 0; r < ranks; ++r)
{
var n = Math.min(left, cols);
for (var c = 0; c < n; ++c)
{
var x = ((n-1)/2 - c) * separation.width;
var z = -r * separation.depth;
offsets.push({"x": x, "z": z});
}
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.width;
var z = -r * separation.depth;
offsets.push({"x": x, "z": z});
}
left -= n;
}
}
// make sure the average offset is zero, as the formation is centered around that
// calculating offset distances without a zero average makes no sense, as the formation
// will jump to a different position any time
@ -870,6 +849,7 @@ Formation.prototype.ComputeMotionParameters = function()
if (cmpUnitMotion)
minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed());
}
minSpeed *= this.GetSpeedMultiplier();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.SetUnitRadius(maxRadius);
@ -981,6 +961,12 @@ Formation.prototype.DeleteTwinFormations = function()
Formation.prototype.LoadFormation = function(formationName)
{
if (formationName == this.formationName)
{
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
cmpUnitAI.MoveIntoFormation();
return;
}
var members = this.members;
this.Disband();
var newFormation = Engine.AddEntity("formations/"+formationName.replace(/\s+/g, "_").toLowerCase());
@ -990,7 +976,11 @@ Formation.prototype.LoadFormation = function(formationName)
var cmpThisUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
var cmpNewUnitAI = Engine.QueryInterface(newFormation, IID_UnitAI);
cmpNewUnitAI.AddOrders(cmpThisUnitAI.GetOrders());
var orders = cmpThisUnitAI.GetOrders();
if (orders.length)
cmpNewUnitAI.AddOrders(orders);
else
cmpNewUnitAI.MoveIntoFormation();
};

View File

@ -2,5 +2,10 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Battle Line</FormationName>
<ShiftRows>true</ShiftRows>
<MinColumns>5</MinColumns>
<MaxColumns>8</MaxColumns>
<MaxRows>6</MaxRows>
<WidthDepthRatio>2</WidthDepthRatio>
</Formation>
</Entity>

View File

@ -2,5 +2,6 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Box</FormationName>
<FormationShape>special</FormationShape>
</Formation>
</Entity>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_formation">
<Formation>
<Formation>
<FormationName>Column Closed</FormationName>
<MinColumns>3</MinColumns>
<MaxColumns>3</MaxColumns>
</Formation>
</Entity>

View File

@ -2,5 +2,9 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Column Open</FormationName>
<ShiftRows>true</ShiftRows>
<UnitSeparationWidthMultiplier>2</UnitSeparationWidthMultiplier>
<MaxColumns>2</MaxColumns>
<MinColumns>2</MinColumns>
</Formation>
</Entity>

View File

@ -2,5 +2,6 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Flank</FormationName>
<FormationShape>special</FormationShape>
</Formation>
</Entity>

View File

@ -2,5 +2,7 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Line Closed</FormationName>
<MinColumns>3</MinColumns>
<WidthDepthRatio>4</WidthDepthRatio>
</Formation>
</Entity>

View File

@ -2,5 +2,9 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Line Open</FormationName>
<ShiftRows>true</ShiftRows>
<UnitSeparationWidthMultiplier>2</UnitSeparationWidthMultiplier>
<WidthDepthRatio>3</WidthDepthRatio>
<MinColumns>3</MinColumns>
</Formation>
</Entity>

View File

@ -2,5 +2,9 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Phalanx</FormationName>
<MinColumns>5</MinColumns>
<MaxColumns>8</MaxColumns>
<MaxRows>6</MaxRows>
<WidthDepthRatio>2</WidthDepthRatio>
</Formation>
</Entity>

View File

@ -2,5 +2,6 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Scatter</FormationName>
<FormationShape>special</FormationShape>
</Formation>
</Entity>

View File

@ -2,5 +2,9 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Skirmish</FormationName>
<ShiftRows>true</ShiftRows>
<UnitSeparationWidthMultiplier>2</UnitSeparationWidthMultiplier>
<MaxRows>2</MaxRows>
<MinColumns>3</MinColumns>
</Formation>
</Entity>

View File

@ -2,5 +2,6 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Wedge</FormationName>
<FormationShape>special</FormationShape>
</Formation>
</Entity>

View File

@ -5,7 +5,14 @@
<Actor>props/special/common/waypoint_flag.xml</Actor>
</VisualActor>
-->
<Formation/>
<Formation>
<SpeedMultiplier>1</SpeedMultiplier>
<FormationShape>square</FormationShape>
<ShiftRows>false</ShiftRows>
<UnitSeparationWidthMultiplier>1</UnitSeparationWidthMultiplier>
<UnitSeparationDepthMultiplier>1</UnitSeparationDepthMultiplier>
<WidthDepthRatio>1</WidthDepthRatio>
</Formation>
<Ownership/>
<Position>
<Altitude>0</Altitude>