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:
parent
aa0d485d72
commit
b8ee766cd3
@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -2,5 +2,6 @@
|
||||
<Entity parent="template_formation">
|
||||
<Formation>
|
||||
<FormationName>Box</FormationName>
|
||||
<FormationShape>special</FormationShape>
|
||||
</Formation>
|
||||
</Entity>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -2,5 +2,6 @@
|
||||
<Entity parent="template_formation">
|
||||
<Formation>
|
||||
<FormationName>Flank</FormationName>
|
||||
<FormationShape>special</FormationShape>
|
||||
</Formation>
|
||||
</Entity>
|
||||
|
@ -2,5 +2,7 @@
|
||||
<Entity parent="template_formation">
|
||||
<Formation>
|
||||
<FormationName>Line Closed</FormationName>
|
||||
<MinColumns>3</MinColumns>
|
||||
<WidthDepthRatio>4</WidthDepthRatio>
|
||||
</Formation>
|
||||
</Entity>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -2,5 +2,6 @@
|
||||
<Entity parent="template_formation">
|
||||
<Formation>
|
||||
<FormationName>Scatter</FormationName>
|
||||
<FormationShape>special</FormationShape>
|
||||
</Formation>
|
||||
</Entity>
|
||||
|
@ -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>
|
||||
|
@ -2,5 +2,6 @@
|
||||
<Entity parent="template_formation">
|
||||
<Formation>
|
||||
<FormationName>Wedge</FormationName>
|
||||
<FormationShape>special</FormationShape>
|
||||
</Formation>
|
||||
</Entity>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user