diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js index 16967b9a5f..d1fecf43ff 100644 --- a/binaries/data/mods/public/simulation/components/Formation.js +++ b/binaries/data/mods/public/simulation/components/Formation.js @@ -3,9 +3,12 @@ function Formation() {} Formation.prototype.Schema = ""; +var g_ColumnDistanceThreshold = 32; // distance at which we'll switch between column/box formations + Formation.prototype.Init = function() { - this.members = []; + this.members = []; // entity IDs currently belonging to this formation + this.columnar = false; // whether we're travelling in column (vs box) formation }; Formation.prototype.GetMemberCount = function() @@ -111,7 +114,12 @@ Formation.prototype.MoveMembersIntoFormation = function() types["Unknown"] += 1; // TODO } - var offsets = this.ComputeFormationOffsets(types); + // Work out whether this should be a column or box formation + var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + var walkingDistance = cmpUnitAI.ComputeWalkingDistance(); + this.columnar = (walkingDistance > g_ColumnDistanceThreshold); + + var offsets = this.ComputeFormationOffsets(types, this.columnar); var avgpos = this.ComputeAveragePosition(positions); var avgoffset = this.ComputeAveragePosition(offsets); @@ -153,22 +161,35 @@ Formation.prototype.MoveToMembersCenter = function() cmpPosition.JumpTo(avgpos.x, avgpos.z); }; -Formation.prototype.ComputeFormationOffsets = function(types) +Formation.prototype.ComputeFormationOffsets = function(types, columnar) { var separation = 4; // TODO: don't hardcode this var count = types["Unknown"]; - // Choose a sensible width for the basic box formation + // Choose a sensible width for the basic default formation var cols; - if (count <= 4) - cols = count; - if (count <= 8) - cols = 4; - else if (count <= 16) - cols = Math.ceil(count / 2); + if (columnar) + { + // Have at most 3 files + if (count <= 3) + cols = count; + else + cols = 3; + } else - cols = 8; + { + // Try to have at least 5 files (so batch training gives a single line), + // and at most 8 + if (count <= 5) + cols = count; + if (count <= 10) + cols = 5; + else if (count <= 16) + cols = Math.ceil(count / 2); + else + cols = 8; + } var ranks = Math.ceil(count / cols); @@ -228,6 +249,16 @@ Formation.prototype.ComputeMotionParameters = function() // TODO: we also need to do something about PassabilityClass, CostClass }; +Formation.prototype.OnUpdate_Final = function(msg) +{ + // Switch between column and box if necessary + var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + var walkingDistance = cmpUnitAI.ComputeWalkingDistance(); + var columnar = (walkingDistance > g_ColumnDistanceThreshold); + if (columnar != this.columnar) + this.MoveMembersIntoFormation(); +}; + Formation.prototype.OnGlobalOwnershipChanged = function(msg) { // When an entity is captured or destroyed, it should no longer be diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index b154894fa1..a5b32c33ae 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -848,6 +848,68 @@ UnitAI.prototype.GetFormationController = function() return this.formationController; }; +/** + * 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(); + + for (var i = 0; i < this.orderQueue.length; ++i) + { + var order = this.orderQueue[i]; + switch (order.type) + { + case "Walk": + // 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; + + break; // and continue the loop + + case "WalkToTarget": + case "Attack": + case "Gather": + case "Repair": + // 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; + + default: + error("Unrecognised order type '"+order.type+"'"); + return distance; + } + } + + // Return the total distance to the end of the order queue + return distance; +}; + UnitAI.prototype.AddOrder = function(type, data, queued) { if (queued)