From 8babdeba04b6c5a421d02f8e42daca8166aba69a Mon Sep 17 00:00:00 2001 From: temple Date: Wed, 7 Feb 2018 20:58:07 +0000 Subject: [PATCH] Spawn units in rally point direction Differential Revision: https://code.wildfiregames.com/D1041 Reviewed by: bb Comments by: elexis, wraitii Fixes: #4735 This was SVN commit r21144. --- .../simulation/components/RallyPoint.js | 10 ++ .../components/interfaces/RallyPoint.js | 1 - .../components/tests/test_Foundation.js | 1 - .../components/tests/test_GarrisonHolder.js | 1 - .../components/tests/test_GuiInterface.js | 1 - .../components/tests/test_RallyPoint.js | 1 - source/simulation2/TypeList.h | 5 +- .../simulation2/components/CCmpFootprint.cpp | 164 +++++++++--------- .../simulation2/components/ICmpRallyPoint.cpp | 45 +++++ .../simulation2/components/ICmpRallyPoint.h | 38 ++++ .../components/ICmpRallyPointRenderer.h | 6 +- source/simulation2/helpers/Geometry.cpp | 42 ++++- source/simulation2/helpers/Geometry.h | 18 +- 13 files changed, 243 insertions(+), 90 deletions(-) delete mode 100644 binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js create mode 100644 source/simulation2/components/ICmpRallyPoint.cpp create mode 100644 source/simulation2/components/ICmpRallyPoint.h diff --git a/binaries/data/mods/public/simulation/components/RallyPoint.js b/binaries/data/mods/public/simulation/components/RallyPoint.js index e3c3148836..e672f11b50 100644 --- a/binaries/data/mods/public/simulation/components/RallyPoint.js +++ b/binaries/data/mods/public/simulation/components/RallyPoint.js @@ -17,6 +17,16 @@ RallyPoint.prototype.AddPosition = function(x, z) }); }; +RallyPoint.prototype.HasPositions = function() +{ + return this.pos.length > 0; +}; + +RallyPoint.prototype.GetFirstPosition = function() +{ + return this.pos.length ? Vector2D.from3D(this.pos[0]) : new Vector2D(-1, -1); +}; + RallyPoint.prototype.GetPositions = function() { // Update positions for moving target entities diff --git a/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js b/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js deleted file mode 100644 index ac0f079ad2..0000000000 --- a/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js +++ /dev/null @@ -1 +0,0 @@ -Engine.RegisterInterface("RallyPoint"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_Foundation.js b/binaries/data/mods/public/simulation/components/tests/test_Foundation.js index 0c61a5a08a..1387aad395 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_Foundation.js +++ b/binaries/data/mods/public/simulation/components/tests/test_Foundation.js @@ -3,7 +3,6 @@ Engine.LoadComponentScript("interfaces/Builder.js"); 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"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js b/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js index a2082ee0ad..91eaee341c 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -10,7 +10,6 @@ Engine.LoadComponentScript("interfaces/ProductionQueue.js"); 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; diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index fb2920f0d6..62a0c897d3 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -22,7 +22,6 @@ Engine.LoadComponentScript("interfaces/Market.js"); 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"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js b/binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js index e25b2ec717..ef33d8d50c 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_RallyPoint.js +++ b/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) diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 93be7f05b8..b08a3cdc91 100644 --- a/source/simulation2/TypeList.h +++ b/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 @@ COMPONENT(Position) // must be before VisualActor INTERFACE(ProjectileManager) COMPONENT(ProjectileManager) +INTERFACE(RallyPoint) +COMPONENT(RallyPointScripted) + INTERFACE(RallyPointRenderer) COMPONENT(RallyPointRenderer) diff --git a/source/simulation2/components/CCmpFootprint.cpp b/source/simulation2/components/CCmpFootprint.cpp index 798e23ef4d..3375a746c9 100644 --- a/source/simulation2/components/CCmpFootprint.cpp +++ b/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 @@ public: 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() { @@ -52,10 +54,12 @@ public: "" "" "0.0" + "8" "" "" "" "0.0" + "8" "" "" "" @@ -80,7 +84,12 @@ public: "" "" "" - ""; + "" + "" + "" + "" + "" + ""; } virtual void Init(const CParamNode& paramNode) @@ -104,6 +113,12 @@ public: } 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 +162,8 @@ public: 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 +172,8 @@ public: 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 +183,85 @@ public: 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) + a *= 2; + b *= 2; + } + + // Store the position of the spawning point within each row that's closest to the spawning angle. + std::vector offsetPoints(rows, 0); + + CmpPtr cmpRallyPoint(GetEntityHandle()); + if (cmpRallyPoint && cmpRallyPoint->HasPositions()) + { + CFixedVector2D rallyPointPos = cmpRallyPoint->GetFirstPosition(); + if (m_Shape == CIRCLE) { - // 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; + entity_angle_t offsetAngle = atan2_approx(rallyPointPos.X - initialPos.X, rallyPointPos.Y - initialPos.Y) - initialAngle; - // 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); + // 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); - fixed s, c; - sincos_approx(angle, s, c); - - CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius)); - - 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; } diff --git a/source/simulation2/components/ICmpRallyPoint.cpp b/source/simulation2/components/ICmpRallyPoint.cpp new file mode 100644 index 0000000000..40ced97b33 --- /dev/null +++ b/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) diff --git a/source/simulation2/components/ICmpRallyPoint.h b/source/simulation2/components/ICmpRallyPoint.h new file mode 100644 index 0000000000..0f0ee19ca8 --- /dev/null +++ b/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 diff --git a/source/simulation2/components/ICmpRallyPointRenderer.h b/source/simulation2/components/ICmpRallyPointRenderer.h index 2133f658d5..12a6a2d521 100644 --- a/source/simulation2/components/ICmpRallyPointRenderer.h +++ b/source/simulation2/components/ICmpRallyPointRenderer.h @@ -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" @@ -56,4 +56,4 @@ public: DECLARE_INTERFACE_TYPE(RallyPointRenderer) }; -#endif // INCLUDED_ICMPRALLYPOINT +#endif // INCLUDED_ICMPRALLYPOINTRENDERER diff --git a/source/simulation2/helpers/Geometry.cpp b/source/simulation2/helpers/Geometry.cpp index 280f43179d..a36cf72b2c 100644 --- a/source/simulation2/helpers/Geometry.cpp +++ b/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 @@ bool Geometry::TestSquareSquare( return true; } + +int Geometry::GetPerimeterDistance(int x_max, int y_max, int x, int y) +{ + if (x_max <= 0 || y_max <= 0) + return 0; + + int quarter = x_max + y_max; + if (x == x_max && y >= 0) + return y; + if (y == y_max) + return quarter - x; + if (x == -x_max) + return 2 * quarter - y; + if (y == -y_max) + return 3 * quarter + x; + if (x == x_max) + return 4 * quarter + y; + return 0; +} + +std::pair Geometry::GetPerimeterCoordinates(int x_max, int y_max, int k) +{ + if (x_max <= 0 || y_max <= 0) + return std::pair(0, 0); + + int quarter = x_max + y_max; + k %= 4 * quarter; + if (k < 0) + k += 4 * quarter; + + if (k < y_max) + return std::pair(x_max, k); + if (k < quarter + x_max) + return std::pair(quarter - k, y_max); + if (k < 2 * quarter + y_max) + return std::pair(-x_max, 2 * quarter - k); + if (k < 3 * quarter + x_max) + return std::pair(k - 3 * quarter, -y_max); + return std::pair(x_max, k - 4 * quarter); +} diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h index 3dc043c3b8..f8b9c60c53 100644 --- a/source/simulation2/helpers/Geometry.h +++ b/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 @@ bool TestSquareSquare( 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 while + * 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