better formation place assignments (units run around less)
This was SVN commit r14281.
This commit is contained in:
parent
3c6045308a
commit
3afd8e9d9e
@ -1 +0,0 @@
|
||||
1 1
|
@ -1 +0,0 @@
|
||||
1 1
|
@ -266,10 +266,8 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force)
|
||||
var walkingDistance = cmpUnitAI.ComputeWalkingDistance();
|
||||
this.columnar = (walkingDistance > g_ColumnDistanceThreshold) && this.formationName != "Column Open";
|
||||
|
||||
var offsets = this.ComputeFormationOffsets(active, this.columnar);
|
||||
|
||||
var avgpos = this.ComputeAveragePosition(positions);
|
||||
var avgoffset = this.ComputeAveragePosition(offsets);
|
||||
|
||||
// Reposition the formation if we're told to or if we don't already have a position
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
@ -285,6 +283,8 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force)
|
||||
}
|
||||
}
|
||||
|
||||
var offsets = this.ComputeFormationOffsets(active, this.columnar);
|
||||
|
||||
for (var i = 0; i < offsets.length; ++i)
|
||||
{
|
||||
var offset = offsets[i];
|
||||
@ -295,16 +295,16 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force)
|
||||
{
|
||||
cmpUnitAI.ReplaceOrder("FormationWalk", {
|
||||
"target": this.entity,
|
||||
"x": offset.x - avgoffset.x,
|
||||
"z": offset.z - avgoffset.z
|
||||
"x": offset.x,
|
||||
"z": offset.z
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
cmpUnitAI.PushOrderFront("FormationWalk", {
|
||||
"target": this.entity,
|
||||
"x": offset.x - avgoffset.x,
|
||||
"z": offset.z - avgoffset.z
|
||||
"x": offset.x,
|
||||
"z": offset.z
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -341,6 +341,8 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar)
|
||||
{
|
||||
var separation = 4; // TODO: don't hardcode this
|
||||
|
||||
// the entities will be assigned to positions in the formation in
|
||||
// the same order as the types list is ordered
|
||||
var types = {
|
||||
"Cavalry" : [],
|
||||
"Hero" : [],
|
||||
@ -348,7 +350,7 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar)
|
||||
"Ranged" : [],
|
||||
"Support" : [],
|
||||
"Unknown": []
|
||||
}; // TODO: make this work so we put ranged behind infantry etc
|
||||
};
|
||||
|
||||
for each (var ent in active)
|
||||
{
|
||||
@ -591,7 +593,7 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar)
|
||||
cols = Math.ceil(count/6);
|
||||
shape = "opensquare";
|
||||
separation /= 1.5;
|
||||
ordering = "cavalryOnTheSides";
|
||||
ordering = "FillFromTheSides";
|
||||
break;
|
||||
default:
|
||||
warn("Unknown formation: " + this.formationName);
|
||||
@ -630,77 +632,99 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar)
|
||||
left -= n;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: assign to minimise worst-case distances or whatever
|
||||
if (ordering == "default")
|
||||
{
|
||||
// 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
|
||||
var avgoffset = this.ComputeAveragePosition(offsets);
|
||||
for each (var offset in offsets)
|
||||
{
|
||||
var ent = undefined;
|
||||
for (var t in types)
|
||||
offset.x -= avgoffset.x;
|
||||
offset.z -= avgoffset.z;
|
||||
}
|
||||
// sort the offsets to the sides are filled first (with cavalry, then heroes ...)
|
||||
if (ordering == "FillFromTheSides")
|
||||
offsets.sort(function(o1, o2) { return Math.abs(o1.x) < Math.abs(o2.x);});
|
||||
|
||||
// use naive but realistic place assignment,
|
||||
// every soldier searches the closest available place in the formation
|
||||
// this isn't the overall smallest walking distance, but the result is realistic
|
||||
var newOffsets = [];
|
||||
var realPositions = this.GetRealOffsetPositions(offsets);
|
||||
for each (var t in types)
|
||||
{
|
||||
if (types[t].length)
|
||||
var usedOffsets = offsets.splice(0,t.length);
|
||||
var usedRealPositions = realPositions.splice(0, t.length);
|
||||
for each (var ent in t)
|
||||
{
|
||||
ent = types[t].pop();
|
||||
offset.ent = ent;
|
||||
break;
|
||||
var closestOffsetId = this.TakeClosestOffset(ent, usedRealPositions);
|
||||
usedRealPositions.splice(closestOffsetId, 1);
|
||||
newOffsets.push(usedOffsets.splice(closestOffsetId, 1)[0]);
|
||||
newOffsets[newOffsets.length - 1].ent = ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ordering == "cavalryOnTheSides")
|
||||
|
||||
return newOffsets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Search the closest position in the realPositions list to the given entity
|
||||
* @param ent, the queried entity
|
||||
* @param realPositions, the world coordinates of the available offsets
|
||||
* @return the index of the closest offset position
|
||||
*/
|
||||
Formation.prototype.TakeClosestOffset = function(ent, realPositions)
|
||||
{
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
var closestOffsetId = -1;
|
||||
var offsetDistanceSq = Infinity;
|
||||
for (var i = 0; i < realPositions.length; i++)
|
||||
{
|
||||
var noffset = [];
|
||||
var cavalrycount = types["Cavalry"].length;
|
||||
offsets.sort(function (a, b) {
|
||||
if (a.x < b.x) return -1;
|
||||
else if (a.x == b.x && a.z < b.z) return -1;
|
||||
return 1;
|
||||
var dx = realPositions[i].x - pos.x;
|
||||
var dz = realPositions[i].z - pos.y;
|
||||
var distSq = dx * dx + dz * dz;
|
||||
if (distSq < offsetDistanceSq)
|
||||
{
|
||||
offsetDistanceSq = distSq;
|
||||
closestOffsetId = i;
|
||||
}
|
||||
}
|
||||
return closestOffsetId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the world positions for a list of offsets in this formation
|
||||
*/
|
||||
Formation.prototype.GetRealOffsetPositions = function(offsets)
|
||||
{
|
||||
var offsetPositions = [];
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
// calculate the rotation of the formation based on the first unitAI target position
|
||||
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
|
||||
var targetPos = cmpUnitAI.GetTargetPositions();
|
||||
// start with default rotation (for formations without target)
|
||||
var sin = 1;
|
||||
var cos = 0;
|
||||
if (targetPos.length)
|
||||
{
|
||||
var dx = targetPos[0].x - pos.x;
|
||||
var dz = targetPos[0].z - pos.y;
|
||||
if (dx || dz)
|
||||
{
|
||||
var dist = Math.sqrt(dx * dx + dz * dz);
|
||||
cos = dx / dist;
|
||||
sin = dz / dist;
|
||||
}
|
||||
}
|
||||
// calculate the world positions
|
||||
for each (var o in offsets)
|
||||
offsetPositions.push({
|
||||
"x" : pos.x + o.z * cos + o.x * sin,
|
||||
"z" : pos.y + o.z * sin - o.x * cos
|
||||
});
|
||||
|
||||
while (offsets.length && types["Cavalry"].length && types["Cavalry"].length > cavalrycount/2)
|
||||
{
|
||||
var offset = offsets.pop();
|
||||
offset.ent = types["Cavalry"].pop();
|
||||
noffset.push(offset);
|
||||
}
|
||||
|
||||
offsets.sort(function (a, b) {
|
||||
if (a.x > b.x) return -1;
|
||||
else if (a.x == b.x && a.z < b.z) return -1;
|
||||
return 1;
|
||||
});
|
||||
|
||||
while (offsets.length && types["Cavalry"].length)
|
||||
{
|
||||
var offset = offsets.pop();
|
||||
offset.ent = types["Cavalry"].pop();
|
||||
noffset.push(offset);
|
||||
}
|
||||
|
||||
offsets.sort(function (a, b) {
|
||||
if (a.z < b.z) return -1;
|
||||
else if (a.z == b.z && a.x < b.x) return -1;
|
||||
return 1;
|
||||
});
|
||||
|
||||
while (offsets.length)
|
||||
{
|
||||
var offset = offsets.pop();
|
||||
for (var t in types)
|
||||
{
|
||||
if (types[t].length)
|
||||
{
|
||||
offset.ent = types[t].pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
noffset.push(offset);
|
||||
}
|
||||
offsets = noffset;
|
||||
}
|
||||
|
||||
return offsets;
|
||||
return offsetPositions;
|
||||
};
|
||||
|
||||
Formation.prototype.ComputeAveragePosition = function(positions)
|
||||
|
@ -4319,22 +4319,9 @@ UnitAI.prototype.MoveIntoFormation = function(cmd)
|
||||
this.PushOrderFront("MoveIntoFormation", { "x": pos.x, "z": pos.z, "force": true });
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the estimated distance that this unit will travel before either
|
||||
* finishing all of its orders, or reaching a non-walk target (attack, gather, etc).
|
||||
* Intended for Formation to switch to column layout on long walks.
|
||||
*/
|
||||
UnitAI.prototype.ComputeWalkingDistance = function()
|
||||
UnitAI.prototype.GetTargetPositions = function()
|
||||
{
|
||||
var distance = 0;
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||
return 0;
|
||||
|
||||
// Keep track of the position at the start of each order
|
||||
var pos = cmpPosition.GetPosition();
|
||||
|
||||
var targetPositions = [];
|
||||
for (var i = 0; i < this.orderQueue.length; ++i)
|
||||
{
|
||||
var order = this.orderQueue[i];
|
||||
@ -4345,15 +4332,7 @@ UnitAI.prototype.ComputeWalkingDistance = function()
|
||||
case "WalkToPointRange":
|
||||
case "MoveIntoFormation":
|
||||
case "GatherNearPosition":
|
||||
// Add the distance to the target point
|
||||
var dx = order.data.x - pos.x;
|
||||
var dz = order.data.z - pos.z;
|
||||
var d = Math.sqrt(dx*dx + dz*dz);
|
||||
distance += d;
|
||||
|
||||
// Remember this as the start position for the next order
|
||||
pos = order.data;
|
||||
|
||||
targetPositions.push({"x" : order.data.x, "z" : order.data.z})
|
||||
break; // and continue the loop
|
||||
|
||||
case "WalkToTarget":
|
||||
@ -4370,26 +4349,47 @@ UnitAI.prototype.ComputeWalkingDistance = function()
|
||||
// Find the target unit's position
|
||||
var cmpTargetPosition = Engine.QueryInterface(order.data.target, IID_Position);
|
||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
|
||||
return distance;
|
||||
var targetPos = cmpTargetPosition.GetPosition();
|
||||
|
||||
// Add the distance to the target unit
|
||||
var dx = targetPos.x - pos.x;
|
||||
var dz = targetPos.z - pos.z;
|
||||
var d = Math.sqrt(dx*dx + dz*dz);
|
||||
distance += d;
|
||||
|
||||
// Return the total distance to the target
|
||||
return distance;
|
||||
return targetPositions;
|
||||
var targetPos = cmpTargetPosition.GetPosition2D();
|
||||
targetPositions.push({"x" : targetPos.x, "z" : targetPos.y})
|
||||
return targetPositions;
|
||||
|
||||
case "Stop":
|
||||
return 0;
|
||||
return [];
|
||||
|
||||
default:
|
||||
error("ComputeWalkingDistance: Unrecognised order type '"+order.type+"'");
|
||||
return distance;
|
||||
error("GetTargetPositions: Unrecognised order type '"+order.type+"'");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return targetPositions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the estimated distance that this unit will travel before either
|
||||
* finishing all of its orders, or reaching a non-walk target (attack, gather, etc).
|
||||
* Intended for Formation to switch to column layout on long walks.
|
||||
*/
|
||||
UnitAI.prototype.ComputeWalkingDistance = function()
|
||||
{
|
||||
var distance = 0;
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||
return 0;
|
||||
|
||||
// Keep track of the position at the start of each order
|
||||
var pos = cmpPosition.GetPosition();
|
||||
var targetPositions = this.GetTargetPositions();
|
||||
for (var i = 0; i < targetPositions; i++)
|
||||
{
|
||||
var dx = targetPositions[i].x - pos.x;
|
||||
var dz = targetPositions[i].z - pos.z;
|
||||
distance += Math.sqrt(dx*dx + dz*dz);
|
||||
|
||||
// Remember this as the start position for the next order
|
||||
pos = targetPositions[i];
|
||||
}
|
||||
|
||||
// Return the total distance to the end of the order queue
|
||||
return distance;
|
||||
|
@ -68,7 +68,8 @@ function TestFormationExiting(mode)
|
||||
});
|
||||
|
||||
AddMock(unit, IID_Position, {
|
||||
GetPosition: function() { return { "x": 0, "z": 0 }; },
|
||||
GetPosition: function() { return { "x": 0, "y": 0,"z": 0 }; },
|
||||
GetPosition2D: function() { return { "x": 0, "y": 0 }; },
|
||||
IsInWorld: function() { return true; },
|
||||
});
|
||||
|
||||
@ -113,6 +114,7 @@ function TestFormationExiting(mode)
|
||||
AddMock(controller, IID_Position, {
|
||||
JumpTo: function(x, z) { this.x = x; this.z = z; },
|
||||
GetPosition: function() { return { "x": this.x, "z": this.z }; },
|
||||
GetPosition2D: function() { return { "x": this.x, "y": this.z }; },
|
||||
IsInWorld: function() { return true; },
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user