Index: binaries/data/mods/public/simulation/components/RallyPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/RallyPoint.js
+++ binaries/data/mods/public/simulation/components/RallyPoint.js
@@ -17,6 +17,16 @@
});
};
+RallyPoint.prototype.HasPositions = function()
+{
+ return this.pos.length > 0;
+};
+
+RallyPoint.prototype.GetFirstPosition = function()
+{
+ return this.pos.length ? { "x": this.pos[0].x, "y": this.pos[0].z } : { "x": -1, "y": -1 };
+};
+
RallyPoint.prototype.GetPositions = function()
{
// Update positions for moving target entities
Index: binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js
+++ /dev/null
@@ -1 +0,0 @@
-Engine.RegisterInterface("RallyPoint");
Index: binaries/data/mods/public/simulation/components/tests/test_Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Foundation.js
+++ binaries/data/mods/public/simulation/components/tests/test_Foundation.js
@@ -3,7 +3,6 @@
Engine.LoadComponentScript("interfaces/Cost.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/Health.js");
-Engine.LoadComponentScript("interfaces/RallyPoint.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
@@ -10,7 +10,6 @@
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
-Engine.LoadComponentScript("interfaces/RallyPoint.js");
const garrisonedEntitiesList = [25, 26, 27, 28, 29, 30, 31, 32, 33];
const garrisonHolderId = 15;
Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -22,7 +22,6 @@
Engine.LoadComponentScript("interfaces/Pack.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
-Engine.LoadComponentScript("interfaces/RallyPoint.js");
Engine.LoadComponentScript("interfaces/Repairable.js");
Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Index: binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js
+++ binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js
@@ -1,7 +1,6 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Health.js");
-Engine.LoadComponentScript("interfaces/RallyPoint.js");
Engine.LoadComponentScript("RallyPoint.js");
function initialRallyPointTest(test_function)
Index: source/simulation2/TypeList.h
===================================================================
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -144,6 +144,9 @@
INTERFACE(ProjectileManager)
COMPONENT(ProjectileManager)
+INTERFACE(RallyPoint)
+COMPONENT(RallyPointScripted)
+
INTERFACE(RallyPointRenderer)
COMPONENT(RallyPointRenderer)
Index: source/simulation2/components/CCmpFootprint.cpp
===================================================================
--- source/simulation2/components/CCmpFootprint.cpp
+++ source/simulation2/components/CCmpFootprint.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -25,9 +25,10 @@
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRallyPoint.h"
#include "simulation2/components/ICmpUnitMotion.h"
+#include "simulation2/helpers/Geometry.h"
#include "simulation2/MessageTypes.h"
-#include "graphics/Terrain.h" // For TERRAIN_TILE_SIZE
#include "maths/FixedVector2D.h"
class CCmpFootprint : public ICmpFootprint
@@ -43,6 +44,7 @@
entity_pos_t m_Size0; // width/radius
entity_pos_t m_Size1; // height/radius
entity_pos_t m_Height;
+ entity_pos_t m_MaxSpawnDistance;
static std::string GetSchema()
{
@@ -80,7 +82,12 @@
""
""
""
- "";
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
}
virtual void Init(const CParamNode& paramNode)
@@ -104,6 +111,12 @@
}
m_Height = paramNode.GetChild("Height").ToFixed();
+
+ if (paramNode.GetChild("MaxSpawnDistance").IsOk())
+ m_MaxSpawnDistance = paramNode.GetChild("MaxSpawnDistance").ToFixed();
+ else
+ // Pick some default
+ m_MaxSpawnDistance = entity_pos_t::FromInt(8);
}
virtual void Deinit()
@@ -147,7 +160,8 @@
if (!cmpObstructionManager)
return error;
- entity_pos_t spawnedRadius;
+ // If no spawned obstruction, use a positive radius to avoid division by zero errors.
+ entity_pos_t spawnedRadius = fixed::FromInt(1);
ICmpObstructionManager::tag_t spawnedTag;
CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned);
@@ -156,9 +170,8 @@
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
- // else use zero radius
- // Get passability class from UnitMotion
+ // Get passability class from UnitMotion.
CmpPtr cmpUnitMotion(GetSimContext(), spawned);
if (!cmpUnitMotion)
return error;
@@ -168,94 +181,85 @@
if (!cmpPathfinder)
return error;
+ // Ignore collisions with the spawned entity.
+ SkipTagObstructionFilter filter(spawnedTag);
+
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
- // Max spawning distance in tiles
- const i32 maxSpawningDistance = 4;
+ CFixedVector2D u = CFixedVector2D(fixed::Zero(), fixed::FromInt(1)).Rotate(initialAngle);
+ CFixedVector2D v = u.Perpendicular();
- if (m_Shape == CIRCLE)
+ // Obstructions are squares, so multiply its radius by 2*sqrt(2) ~= 3 to determine the distance between units.
+ entity_pos_t gap = spawnedRadius * 3;
+ int rows = std::max(1, (m_MaxSpawnDistance / gap).ToInt_RoundToNegInfinity());
+
+ // The first row of units will be half a gap away from the footprint.
+ CFixedVector2D halfSize = m_Shape == CIRCLE ?
+ CFixedVector2D(m_Size1 + gap / 2, m_Size0 + gap / 2) :
+ CFixedVector2D((m_Size1 + gap) / 2, (m_Size0 + gap) / 2);
+
+ // Figure out how many units can fit on each halfside of the rectangle.
+ // Since 2*pi/6 ~= 1, this is also how many units can fit on a sixth of the circle.
+ int a = std::max(1, (halfSize.X / gap).ToInt_RoundToNegInfinity());
+ int b = std::max(1, (halfSize.Y / gap).ToInt_RoundToNegInfinity());
+
+ // Try more spawning points for large units in case some of them are partially blocked.
+ if (rows == 1)
{
- // Expand outwards from foundation
- for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
- {
- // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
- entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist);
- entity_pos_t radius = m_Size0 + clearance;
+ a *= 2;
+ b *= 2;
+ }
- // Try equally-spaced points around the circle in alternating directions, starting from the front
- const i32 numPoints = 31 + 2*dist;
- for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
- {
- entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
+ // Store the position of the spawning point within each row that's closest to the spawning angle.
+ std::vector offsetPoints(rows, 0);
- fixed s, c;
- sincos_approx(angle, s, c);
+ CmpPtr cmpRallyPoint(GetEntityHandle());
+ if (cmpRallyPoint && cmpRallyPoint->HasPositions())
+ {
+ CFixedVector2D rallyPointPos = cmpRallyPoint->GetFirstPosition();
+ if (m_Shape == CIRCLE)
+ {
+ entity_angle_t offsetAngle = atan2_approx(rallyPointPos.X - initialPos.X, rallyPointPos.Y - initialPos.Y) - initialAngle;
- CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
+ // There are 6*(a+r) points in row r, so multiply that by angle/2pi to find the offset within the row.
+ for (int r = 0; r < rows; ++r)
+ offsetPoints[r] = (offsetAngle * 3 * (a + r) / fixed::Pi()).ToInt_RoundToNearest();
+ }
+ else
+ {
+ CFixedVector2D offsetPos = Geometry::NearestPointOnSquare(rallyPointPos - initialPos, u, v, halfSize);
- SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
- if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
- return pos; // this position is okay, so return it
- }
+ // Scale and convert the perimeter coordinates of the point to its offset within the row.
+ int x = (offsetPos.Dot(u) * b / halfSize.X).ToInt_RoundToNearest();
+ int y = (offsetPos.Dot(v) * a / halfSize.Y).ToInt_RoundToNearest();
+ for (int r = 0; r < rows; ++r)
+ offsetPoints[r] = Geometry::GetPerimeterDistance(
+ b + r,
+ a + r,
+ x >= b ? b + r : x <= -b ? -b - r : x,
+ y >= a ? a + r : y <= -a ? -a - r : y);
}
}
- else
- {
- fixed s, c;
- sincos_approx(initialAngle, s, c);
- // Expand outwards from foundation
- for (i32 dist = 0; dist <= maxSpawningDistance; ++dist)
+ for (int k = 0; k < 2 * (a + b + 2 * rows); k = k > 0 ? -k : 1 - k)
+ for (int r = 0; r < rows; ++r)
{
- // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
- entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist);
-
- for (i32 edge = 0; edge < 4; ++edge)
+ CFixedVector2D pos = initialPos;
+ if (m_Shape == CIRCLE)
+ // Multiply the point by 2pi / 6*(a+r) to get the angle.
+ pos += u.Rotate(fixed::Pi() * (offsetPoints[r] + k) / (3 * (a + r))).Multiply(halfSize.X + gap * r );
+ else
{
- // Try equally-spaced points along the edge in alternating directions, starting from the middle
- const i32 numPoints = 9 + 2*dist;
-
- // Compute the direction and length of the current edge
- CFixedVector2D dir;
- fixed sx, sy;
- switch (edge)
- {
- case 0:
- dir = CFixedVector2D(c, -s);
- sx = m_Size0;
- sy = m_Size1;
- break;
- case 1:
- dir = CFixedVector2D(-s, -c);
- sx = m_Size1;
- sy = m_Size0;
- break;
- case 2:
- dir = CFixedVector2D(s, c);
- sx = m_Size1;
- sy = m_Size0;
- break;
- case 3:
- dir = CFixedVector2D(-c, s);
- sx = m_Size0;
- sy = m_Size1;
- break;
- }
- CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
- dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
-
- for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
- {
- CFixedVector2D pos (center + dir*i);
-
- SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
- if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
- return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
- }
+ // Convert the point to coordinates and scale.
+ std::pair p = Geometry::GetPerimeterCoordinates(b + r, a + r, offsetPoints[r] + k);
+ pos += u.Multiply((halfSize.X + gap * r) * p.first / (b + r)) +
+ v.Multiply((halfSize.Y + gap * r) * p.second / (a + r));
}
+
+ if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
+ return CFixedVector3D(pos.X, fixed::Zero(), pos.Y);
}
- }
return error;
}
Index: source/simulation2/components/ICmpRallyPoint.h
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpRallyPoint.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2018 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPRALLYPOINT
+#define INCLUDED_ICMPRALLYPOINT
+
+#include "simulation2/system/Interface.h"
+
+class CFixedVector2D;
+
+/**
+ * Rally point.
+ */
+class ICmpRallyPoint : public IComponent
+{
+public:
+ virtual bool HasPositions() = 0;
+
+ virtual CFixedVector2D GetFirstPosition() = 0;
+
+ DECLARE_INTERFACE_TYPE(RallyPoint)
+};
+
+#endif // INCLUDED_ICMPRALLYPOINT
Index: source/simulation2/components/ICmpRallyPoint.cpp
===================================================================
--- /dev/null
+++ source/simulation2/components/ICmpRallyPoint.cpp
@@ -0,0 +1,45 @@
+/* Copyright (C) 2018 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpRallyPoint.h"
+
+#include "maths/FixedVector2D.h"
+#include "simulation2/system/InterfaceScripted.h"
+#include "simulation2/scripting/ScriptComponent.h"
+
+BEGIN_INTERFACE_WRAPPER(RallyPoint)
+END_INTERFACE_WRAPPER(RallyPoint)
+
+class CCmpRallyPointScripted : public ICmpRallyPoint
+{
+public:
+ DEFAULT_SCRIPT_WRAPPER(RallyPointScripted)
+
+ virtual bool HasPositions()
+ {
+ return m_Script.Call("HasPositions");
+ }
+
+ virtual CFixedVector2D GetFirstPosition()
+ {
+ return m_Script.Call("GetFirstPosition");
+ }
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(RallyPointScripted)
Index: source/simulation2/components/ICmpRallyPointRenderer.h
===================================================================
--- source/simulation2/components/ICmpRallyPointRenderer.h
+++ source/simulation2/components/ICmpRallyPointRenderer.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -15,8 +15,8 @@
* along with 0 A.D. If not, see .
*/
-#ifndef INCLUDED_ICMPRALLYPOINT
-#define INCLUDED_ICMPRALLYPOINT
+#ifndef INCLUDED_ICMPRALLYPOINTRENDERER
+#define INCLUDED_ICMPRALLYPOINTRENDERER
#include "maths/FixedVector2D.h"
#include "simulation2/helpers/Position.h"
@@ -53,4 +53,4 @@
DECLARE_INTERFACE_TYPE(RallyPointRenderer)
};
-#endif // INCLUDED_ICMPRALLYPOINT
+#endif // INCLUDED_ICMPRALLYPOINTRENDERER
Index: source/simulation2/helpers/Geometry.h
===================================================================
--- source/simulation2/helpers/Geometry.h
+++ source/simulation2/helpers/Geometry.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -99,6 +99,22 @@
const CFixedVector2D& c0, const CFixedVector2D& u0, const CFixedVector2D& v0, const CFixedVector2D& halfSize0,
const CFixedVector2D& c1, const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1);
+/**
+ * Used in Footprint when spawning units:
+ * Given a grid point (x, y) on the rectangle [-x_max, x_max] x [-y_max, y_max],
+ * this returns the distance travelled in moving from (x_max, 0) to the the point
+ * and walking counter-clockwise along the perimeter of the rectangle.
+ */
+int GetPerimeterDistance(int x_max, int y_max, int x, int y);
+
+/**
+ * Used in Footprint when spawning units:
+ * This returns the grid point on the rectangle [-x_max, x_max] x [-y_max, y_max]
+ * reached after starting at (x_max, 0) and walking a distance k
+ * counter-clockwise along the perimeter of the rectangle.
+ */
+std::pair GetPerimeterCoordinates(int x_max, int y_max, int k);
+
} // namespace
#endif // INCLUDED_HELPER_GEOMETRY
Index: source/simulation2/helpers/Geometry.cpp
===================================================================
--- source/simulation2/helpers/Geometry.cpp
+++ source/simulation2/helpers/Geometry.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -317,3 +317,43 @@
return true;
}
+
+int Geometry::GetPerimeterDistance(int x_max, int y_max, int x, int y)
+{
+ int quarter = x_max + y_max;
+ if (quarter <= 0)
+ return 0;
+
+ if (x == x_max && y >= 0)
+ return y;
+ else if (y == y_max)
+ return quarter - x;
+ else if (x == -x_max)
+ return 2 * quarter - y;
+ else if (y == -y_max)
+ return 3 * quarter + x;
+ else if (x == x_max)
+ return 4 * quarter + y;
+ return 0;
+}
+
+std::pair Geometry::GetPerimeterCoordinates(int x_max, int y_max, int k)
+{
+ int quarter = x_max + y_max;
+ if (quarter <= 0)
+ return std::pair(0, 0);
+
+ k %= 4 * quarter;
+ if (k < 0)
+ k += 4 * quarter;
+
+ if (k < y_max)
+ return std::pair(x_max, k);
+ else if (k < quarter + x_max)
+ return std::pair(quarter - k, y_max);
+ else if (k < 2 * quarter + y_max)
+ return std::pair(-x_max, 2 * quarter - k);
+ else if (k < 3 * quarter + x_max)
+ return std::pair(k - 3 * quarter, -y_max);
+ return std::pair(x_max, k - 4 * quarter);
+}