From 3afd8e9d9e661a24626a02e141cedd63dfc964c6 Mon Sep 17 00:00:00 2001 From: sanderd17 Date: Wed, 4 Dec 2013 13:14:31 +0000 Subject: [PATCH] better formation place assignments (units run around less) This was SVN commit r14281. --- .../cursors/action-guard-disabled.txt | 1 - .../textures/cursors/action-remove-guard.txt | 1 - .../public/simulation/components/Formation.js | 164 ++++++++++-------- .../public/simulation/components/UnitAI.js | 76 ++++---- .../components/tests/test_UnitAI.js | 4 +- 5 files changed, 135 insertions(+), 111 deletions(-) delete mode 100644 binaries/data/mods/public/art/textures/cursors/action-guard-disabled.txt delete mode 100644 binaries/data/mods/public/art/textures/cursors/action-remove-guard.txt diff --git a/binaries/data/mods/public/art/textures/cursors/action-guard-disabled.txt b/binaries/data/mods/public/art/textures/cursors/action-guard-disabled.txt deleted file mode 100644 index 2fb73a07ec..0000000000 --- a/binaries/data/mods/public/art/textures/cursors/action-guard-disabled.txt +++ /dev/null @@ -1 +0,0 @@ -1 1 diff --git a/binaries/data/mods/public/art/textures/cursors/action-remove-guard.txt b/binaries/data/mods/public/art/textures/cursors/action-remove-guard.txt deleted file mode 100644 index 2fb73a07ec..0000000000 --- a/binaries/data/mods/public/art/textures/cursors/action-remove-guard.txt +++ /dev/null @@ -1 +0,0 @@ -1 1 diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js index 71c5383b8e..3b2409a629 100644 --- a/binaries/data/mods/public/simulation/components/Formation.js +++ b/binaries/data/mods/public/simulation/components/Formation.js @@ -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) { - for each (var offset in offsets) + 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) + { + var usedOffsets = offsets.splice(0,t.length); + var usedRealPositions = realPositions.splice(0, t.length); + for each (var ent in t) { - var ent = undefined; - for (var t in types) - { - if (types[t].length) - { - 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; - }); - - while (offsets.length && types["Cavalry"].length && types["Cavalry"].length > cavalrycount/2) + var dx = realPositions[i].x - pos.x; + var dz = realPositions[i].z - pos.y; + var distSq = dx * dx + dz * dz; + if (distSq < offsetDistanceSq) { - var offset = offsets.pop(); - offset.ent = types["Cavalry"].pop(); - noffset.push(offset); + offsetDistanceSq = distSq; + closestOffsetId = i; } - - 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 closestOffsetId; +}; - return offsets; +/** + * 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 + }); + + return offsetPositions; }; Formation.prototype.ComputeAveragePosition = function(positions) diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 6a48dd588c..eff885a6ec 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -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; diff --git a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js index ee322e45ca..07d4ebe741 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -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; }, });