diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 8ac9142b3f2..189a36860ea 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -191,11 +191,6 @@ function getActionInfo(action, target) // e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse) var targetState = GetEntityState(target); - // If we selected buildings with rally points, and then click on one of those selected - // buildings, we should remove the rally point - //if (haveRallyPoints && selection.indexOf(target) != -1) - // return {"type": "unset-rallypoint"}; - // Check if the target entity is a resource, dropsite, foundation, or enemy unit. // Check if any entities in the selection can gather the requested resource, // can return to the dropsite, can build the foundation, or can attack the enemy @@ -1317,12 +1312,13 @@ function doAction(action, ev) { pos = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); } - Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": pos.x, "z": pos.z, "data": action.data}); + Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": pos.x, "z": pos.z, "data": action.data, "queued": queued}); // Display rally point at the new coordinates, to avoid display lag Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": selection, "x": pos.x, - "z": pos.z + "z": pos.z, + "queued": queued }); return true; diff --git a/binaries/data/mods/public/simulation/components/GarrisonHolder.js b/binaries/data/mods/public/simulation/components/GarrisonHolder.js index 60a15b0b9a1..97573675bfd 100644 --- a/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ b/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -215,10 +215,14 @@ GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities) var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); if (cmpRallyPoint) { - var rallyPos = cmpRallyPoint.GetPosition(); + var rallyPos = cmpRallyPoint.GetPositions()[0]; if (rallyPos) { - ProcessCommand(cmpOwnership.GetOwner(), GetRallyPointCommand(cmpRallyPoint, entities)); + var commands = GetRallyPointCommands(cmpRallyPoint, entities); + for each (var com in commands) + { + ProcessCommand(cmpOwnership.GetOwner(), com); + } } } }; @@ -295,7 +299,7 @@ GarrisonHolder.prototype.OnHealthChanged = function(msg) cmpHealth.Kill(); } } - this.entities = []; + this.entities = []; Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); } else diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 89c6a4b65df..4ce8c7126d5 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -259,7 +259,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent) var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) { - ret.rallyPoint = {'position': cmpRallyPoint.GetPosition()}; // undefined or {x,z} object + ret.rallyPoint = {'position': cmpRallyPoint.GetPositions()[0]}; // undefined or {x,z} object } var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); @@ -629,7 +629,7 @@ GuiInterface.prototype.SetStatusBars = function(player, cmd) }; /** - * Displays the rally point of a given list of entities (carried in cmd.entities). + * Displays the rally points of a given list of entities (carried in cmd.entities). * * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should * be rendered, in order to support instantaneously rendering a rally point marker at a specified location @@ -677,11 +677,15 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) if (cmd.x && cmd.z) pos = cmd; else - pos = cmpRallyPoint.GetPosition(); // may return undefined if no rally point is set + pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set if (pos) { - cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z + // Only update the position if we changed it (cmd.queued is set) + if (cmd.queued == true) + cmpRallyPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z + else if (cmd.queued == false) + cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z cmpRallyPointRenderer.SetDisplayed(true); // remember which entities have their rally points displayed so we can hide them again diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js index feb3b1dc665..ec1e7866a29 100644 --- a/binaries/data/mods/public/simulation/components/ProductionQueue.js +++ b/binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -442,10 +442,14 @@ ProductionQueue.prototype.SpawnUnits = function(templateName, count, metadata) // rally point is placed. if (cmpRallyPoint) { - var rallyPos = cmpRallyPoint.GetPosition(); + var rallyPos = cmpRallyPoint.GetPositions()[0]; if (rallyPos) { - ProcessCommand(cmpOwnership.GetOwner(), GetRallyPointCommand(cmpRallyPoint, spawnedEnts)); + var commands = GetRallyPointCommands(cmpRallyPoint, spawnedEnts); + for each(var com in commands) + { + ProcessCommand(cmpOwnership.GetOwner(), com); + } } } diff --git a/binaries/data/mods/public/simulation/components/RallyPoint.js b/binaries/data/mods/public/simulation/components/RallyPoint.js index 48292108c8a..49203d35ddb 100644 --- a/binaries/data/mods/public/simulation/components/RallyPoint.js +++ b/binaries/data/mods/public/simulation/components/RallyPoint.js @@ -5,30 +5,31 @@ RallyPoint.prototype.Schema = RallyPoint.prototype.Init = function() { - this.pos = undefined; + this.pos = []; + this.data = []; }; -RallyPoint.prototype.SetPosition = function(x, z) +RallyPoint.prototype.AddPosition = function(x, z) { - this.pos = { + this.pos.push({ "x": x, "z": z - }; + }); }; -RallyPoint.prototype.GetPosition = function() +RallyPoint.prototype.GetPositions = function() { return this.pos; }; // Extra data for the rally point, should have a command property and then helpful data for that command // See getActionInfo in gui/input.js -RallyPoint.prototype.SetData = function(data) +RallyPoint.prototype.AddData = function(data) { - this.data = data; + this.data.push(data); }; -// Returns the data associated with this rally point. Uses the data structure: +// Returns an array with the data associated with this rally point. Each element has the structure: // {"type": "walk/gather/garrison/...", "target": targetEntityId, "resourceType": "tree/fruit/ore/..."} where target // and resourceType (specific resource type) are optional, also target may be an invalid entity, check for existence. RallyPoint.prototype.GetData = function() @@ -38,9 +39,8 @@ RallyPoint.prototype.GetData = function() RallyPoint.prototype.Unset = function() { - this.pos = undefined; - this.data = undefined; + this.pos = []; + this.data = []; }; - Engine.RegisterComponentType(IID_RallyPoint, "RallyPoint", RallyPoint); diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index fec25c189a9..c5af2ca2f60 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -232,8 +232,11 @@ function ProcessCommand(player, cmd) var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) { - cmpRallyPoint.SetPosition(cmd.x, cmd.z); - cmpRallyPoint.SetData(cmd.data); + if (!cmd.queued) + cmpRallyPoint.Unset(); + + cmpRallyPoint.AddPosition(cmd.x, cmd.z); + cmpRallyPoint.AddData(cmd.data); } } break; diff --git a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js index b457517173c..17e8cafa30c 100644 --- a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js +++ b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js @@ -1,67 +1,76 @@ -// Returns an command suitable for ProcessCommand() based on the rally point data. +// Returns an array of commands suitable for ProcessCommand() based on the rally point data. // This assumes that the rally point has a valid position. -function GetRallyPointCommand(cmpRallyPoint, spawnedEnts) +function GetRallyPointCommands(cmpRallyPoint, spawnedEnts) { - // Look and see if there is a command in the rally point data, otherwise just walk there. var data = cmpRallyPoint.GetData(); - var rallyPos = cmpRallyPoint.GetPosition(); - var command = undefined; - if (data && data.command) + var rallyPos = cmpRallyPoint.GetPositions(); + var ret = []; + for(var i = 0; i < rallyPos.length; ++i) { - command = data.command; - } - else - { - command = "walk"; - } - - // If a target was set and the target no longer exists, or no longer - // has a valid position, then just walk to the rally point. - if (data && data.target) - { - var cmpPosition = Engine.QueryInterface(data.target, IID_Position); - if (!cmpPosition || !cmpPosition.IsInWorld()) + // Look and see if there is a command in the rally point data, otherwise just walk there. + var command = undefined; + if (data[i] && data[i].command) + { + command = data[i].command; + } + else { command = "walk"; } - } - switch (command) - { - case "gather": - return { - "type": "gather-near-position", - "entities": spawnedEnts, - "x": rallyPos.x, - "z": rallyPos.z, - "resourceType": data.resourceType, - "queued": false - }; - case "repair": - case "build": - return { - "type": "repair", - "entities": spawnedEnts, - "target": data.target, - "queued": false, - "autocontinue": true - }; - case "garrison": - return { - "type": "garrison", - "entities": spawnedEnts, - "target": data.target, - "queued": false - }; - default: - return { - "type": "walk", - "entities": spawnedEnts, - "x": rallyPos.x, - "z": rallyPos.z, - "queued": false - }; + // If a target was set and the target no longer exists, or no longer + // has a valid position, then just walk to the rally point. + if (data[i] && data[i].target) + { + var cmpPosition = Engine.QueryInterface(data[i].target, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld()) + { + command = "walk"; + } + } + + switch (command) + { + case "gather": + ret.push( { + "type": "gather-near-position", + "entities": spawnedEnts, + "x": rallyPos[i].x, + "z": rallyPos[i].z, + "resourceType": data[i].resourceType, + "queued": true + }); + break; + case "repair": + case "build": + ret.push( { + "type": "repair", + "entities": spawnedEnts, + "target": data[i].target, + "queued": true, + "autocontinue": (i == rallyPos.length-1) + }); + break; + case "garrison": + ret.push( { + "type": "garrison", + "entities": spawnedEnts, + "target": data[i].target, + "queued": true + }); + break; + default: + ret.push( { + "type": "walk", + "entities": spawnedEnts, + "x": rallyPos[i].x, + "z": rallyPos[i].z, + "queued": true + }); + break; + } } + return ret; } -Engine.RegisterGlobal("GetRallyPointCommand", GetRallyPointCommand); +Engine.RegisterGlobal("GetRallyPointCommands", GetRallyPointCommands); diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp index 467ff1cea03..8612c1512ee 100644 --- a/source/simulation2/components/CCmpRallyPointRenderer.cpp +++ b/source/simulation2/components/CCmpRallyPointRenderer.cpp @@ -81,29 +81,30 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer componentManager.SubscribeToMessageType(MT_OwnershipChanged); componentManager.SubscribeToMessageType(MT_TurnStart); componentManager.SubscribeToMessageType(MT_Destroy); - // TODO: should probably also listen to movement messages (unlikely to happen in-game, but might occur inside atlas) + componentManager.SubscribeToMessageType(MT_PositionChanged); } DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer) protected: - /// Display position of the rally point. Note that this is merely the display position; it is not necessarily the same as the - /// actual position used in the simulation at any given time. In particular, we need this separate copy to support - /// instantaneously rendering the rally point marker/line when the user sets one in-game (instead of waiting until the + /// Display position of the rally points. Note that this are merely the display positions; they not necessarily the same as the + /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support + /// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second). - CFixedVector2D m_RallyPoint; - /// Full path to the rally point as returned by the pathfinder, with some post-processing applied to reduce zig/zagging. - std::vector m_Path; - /// Visibility segments of the rally point path; splits the path into SoD/non-SoD segments. - std::deque m_VisibilitySegments; + std::vector m_RallyPoints; + /// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging. + std::vector > m_Path; + /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments. + std::deque > m_VisibilitySegments; - bool m_Displayed; ///< Should we render the rally point and its path line? (set from JS when e.g. the unit is selected/deselected) + bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected) bool m_SmoothPath; ///< Smooth the path before rendering? - entity_id_t m_MarkerEntityId; ///< Entity ID of the rally point marker. Allocated when first displayed. + std::vector m_MarkerEntityIds; ///< Entity IDs of the rally point markers. + size_t m_LastMarkerCount; player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes). - std::wstring m_MarkerTemplate; ///< Template name of the rally point marker. + std::wstring m_MarkerTemplate; ///< Template name of the rally point markers. /// Marker connector line settings (loaded from XML) float m_LineThickness; @@ -121,11 +122,11 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render /// dashes for segments that are inside the SoD. - std::vector m_TexturedOverlayLines; + std::vector > m_TexturedOverlayLines; /// Draw little overlay circles to indicate where the exact path points are? bool m_EnableDebugNodeOverlay; - std::vector m_DebugNodeOverlays; + std::vector > m_DebugNodeOverlays; public: @@ -232,7 +233,7 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer break; case MT_OwnershipChanged: { - UpdateMarker(); // update marker variation to new player's civilization + UpdateMarkers(); // update marker variation to new player's civilization } break; case MT_TurnStart: @@ -242,23 +243,37 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer break; case MT_Destroy: { - if (m_MarkerEntityId != INVALID_ENTITY) + for (std::vector::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it) { - GetSimContext().GetComponentManager().DestroyComponentsSoon(m_MarkerEntityId); - m_MarkerEntityId = INVALID_ENTITY; + if (*it != INVALID_ENTITY) + { + GetSimContext().GetComponentManager().DestroyComponentsSoon(*it); + *it = INVALID_ENTITY; + } } } break; + case MT_PositionChanged: + { + // Unlikely to happen in-game, but can occur in atlas + // Just recompute the path from the entity to the first rally point + RecomputeRallyPointPath_wrapper(0); + } + break; } } + virtual void AddPosition_wrapper(CFixedVector2D pos) + { + AddPosition(pos, false); + } + virtual void SetPosition(CFixedVector2D pos) { - if (m_RallyPoint != pos) + if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos)) { - m_RallyPoint = pos; - UpdateMarker(); // reposition the marker - RecomputeRallyPointPath(); + m_RallyPoints.clear(); + AddPosition(pos, true); } } @@ -268,8 +283,8 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer { m_Displayed = displayed; - // move the marker out of oblivion and back into the real world, or vice-versa - UpdateMarker(); + // move the markers out of oblivion and back into the real world, or vice-versa + UpdateMarkers(); // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred @@ -281,41 +296,77 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer private: /** - * Returns true iff a display rally point is set; i.e., if we have a point to render our marker/line at. + * Helper function for AddPosition_wrapper and SetPosition. + */ + void AddPosition(CFixedVector2D pos, bool recompute) + { + m_RallyPoints.push_back(pos); + UpdateMarkers(); + + if (recompute) + RecomputeAllRallyPointPaths(); + else + RecomputeRallyPointPath_wrapper(m_RallyPoints.size()-1); + } + + /** + * Returns true iff at least one display rally point is set; i.e., if we have a point to render our marker/line at. */ bool IsSet() { - return !m_RallyPoint.IsZero(); + return !m_RallyPoints.empty(); } /** - * Repositions the rally point marker; moves it outside of the world (ie. hides it), or positions it at the currently set rally - * point. Also updates the actor's variation according to the entity's current owning player's civilization. + * Repositions the rally point markers; moves them outside of the world (ie. hides them), or positions them at the currently + * set rally points. Also updates the actor's variation according to the entity's current owning player's civilization. * - * Should be called whenever either the position of the rally point changes (including whether it is set or not), or the display + * Should be called whenever either the position of a rally point changes (including whether it is set or not), or the display * flag changes, or the ownership of the entity changes. */ - void UpdateMarker(); + void UpdateMarkers(); + + /** + * Recomputes all the full paths from this entity to the rally point and from the rally point to the next, and does all the necessary + * post-processing to make them prettier. + * + * Should be called whenever all rally points' position changes. + */ + void RecomputeAllRallyPointPaths(); + + /** + * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier. + * + * Should be called whenever either the starting position or the rally point's position changes. + */ + void RecomputeRallyPointPath_wrapper(size_t index); /** - * Recomputes the full path from this entity to the rally point, and does all the necessary post-processing to make it prettier. - * Should be called whenever the rally point position changes. + * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary + * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set. + * + * You shouldn't need to call this method directly. */ - void RecomputeRallyPointPath(); + void RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder); /** * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to - * match if necessary. Does nothing if the rally point line is not currently set to be displayed, or if the rally point is - * not set. + * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set. */ void UpdateOverlayLines(); /** - * Sets up the overlay lines for rendering according to the current full path and visibility segments. Does all the necessary - * splitting of the line into solid and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full - * path is currently set, this method does nothing. + * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid + * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method + * does nothing. */ - void ConstructOverlayLines(); + void ConstructAllOverlayLines(); + + /** + * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into + * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed. + */ + void ConstructOverlayLines(size_t index); /** * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point @@ -325,16 +376,10 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint); /** - * Removes points from @p coords that are inside the shroud of darkness, i.e. where the player shouldn't be able to get any - * information about the positions of various buildings and whatnot from the rally point path. - */ - void FixInvisibleWaypoints(std::vector& coords); - - /** - * Returns a list of indices of waypoints in the current path (m_FullPath) where the LOS visibility changes, ordered from - * building to rally point. Used to construct the overlay line segments and track changes to the shroud of darkness. + * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from + * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD. */ - void GetVisibilitySegments(std::deque& out); + void GetVisibilitySegments(std::deque& out, size_t index); /** * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily @@ -366,7 +411,7 @@ void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) m_Displayed = false; m_SmoothPath = true; m_LastOwner = INVALID_PLAYER; - m_MarkerEntityId = INVALID_ENTITY; + m_LastMarkerCount = 0; m_EnableDebugNodeOverlay = false; // --------------------------------------------------------------------------------------------- @@ -415,70 +460,84 @@ void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) } } -void CCmpRallyPointRenderer::UpdateMarker() +void CCmpRallyPointRenderer::UpdateMarkers() { - if (m_MarkerEntityId == INVALID_ENTITY) + player_id_t previousOwner = m_LastOwner; + for (size_t i = 0; i < m_RallyPoints.size(); ++i) { - // no marker exists yet, create one first - CComponentManager& componentMgr = GetSimContext().GetComponentManager(); + if (i >= m_MarkerEntityIds.size()) + m_MarkerEntityIds.push_back(INVALID_ENTITY); - // allocate a new entity for the marker - if (!m_MarkerTemplate.empty()) + if (m_MarkerEntityIds[i] == INVALID_ENTITY) { - m_MarkerEntityId = componentMgr.AllocateNewLocalEntity(); - if (m_MarkerEntityId != INVALID_ENTITY) - m_MarkerEntityId = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityId); + // no marker exists yet, create one first + CComponentManager& componentMgr = GetSimContext().GetComponentManager(); + + // allocate a new entity for the marker + if (!m_MarkerTemplate.empty()) + { + m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity(); + if (m_MarkerEntityIds[i] != INVALID_ENTITY) + m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]); + } } - } - // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it - if (m_MarkerEntityId == INVALID_ENTITY) - LOGERROR(L"Failed to create rally point marker entity"); + // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it + if (m_MarkerEntityIds[i] == INVALID_ENTITY) + LOGERROR(L"Failed to create rally point marker entity"); - CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityId); - if (markerCmpPosition) - { - if (m_Displayed && IsSet()) - { - markerCmpPosition->JumpTo(m_RallyPoint.X, m_RallyPoint.Y); - } - else + CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]); + if (markerCmpPosition) { - markerCmpPosition->MoveOutOfWorld(); // hide it + if (m_Displayed && IsSet()) + { + markerCmpPosition->JumpTo(m_RallyPoints[i].X, m_RallyPoints[i].Y); + } + else + { + markerCmpPosition->MoveOutOfWorld(); // hide it + } } - } - // set rally point flag selection based on player civilization - CmpPtr cmpOwnership(GetSimContext(), GetEntityId()); - if (cmpOwnership) - { - player_id_t ownerId = cmpOwnership->GetOwner(); - if (ownerId != INVALID_PLAYER && ownerId != m_LastOwner) + // set rally point flag selection based on player civilization + CmpPtr cmpOwnership(GetSimContext(), GetEntityId()); + if (cmpOwnership) { - m_LastOwner = ownerId; - CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); - // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time - // (we can't rely on component initialization order in Init()) - if (cmpPlayerManager) + player_id_t ownerId = cmpOwnership->GetOwner(); + if (ownerId != INVALID_PLAYER && (ownerId != previousOwner || m_LastMarkerCount < i)) { - CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId)); - if (cmpPlayer) + m_LastOwner = ownerId; + CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); + // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time + // (we can't rely on component initialization order in Init()) + if (cmpPlayerManager) { - CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityId); - if (cmpVisualActor) + CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId)); + if (cmpPlayer) { - cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8()); + CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]); + if (cmpVisualActor) + { + cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8()); + } } } } } } + m_LastMarkerCount = m_RallyPoints.size() - 1; } -void CCmpRallyPointRenderer::RecomputeRallyPointPath() +void CCmpRallyPointRenderer::RecomputeAllRallyPointPaths() { m_Path.clear(); m_VisibilitySegments.clear(); + m_TexturedOverlayLines.clear(); + + //// /////////////////////////////////////////////// + if (m_EnableDebugNodeOverlay) + m_DebugNodeOverlays.clear(); + //// ////////////////////////////////////////////// if (!IsSet()) return; // no use computing a path if the rally point isn't set @@ -488,31 +547,82 @@ void CCmpRallyPointRenderer::RecomputeRallyPointPath() return; // no point going on if this entity doesn't have a position or is outside of the world CmpPtr cmpFootprint(GetSimContext(), GetEntityId()); - CmpPtr cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); + CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); + + for (size_t i = 0; i < m_RallyPoints.size(); ++i) + { + RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder); + } +} - // ------------------------------------------------------------------------------------------------- +void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index) +{ + if (!IsSet()) + return; // no use computing a path if the rally point isn't set + + CmpPtr cmpPosition(GetSimContext(), GetEntityId()); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return; // no point going on if this entity doesn't have a position or is outside of the world - entity_pos_t pathStartX = cmpPosition->GetPosition2D().X; - entity_pos_t pathStartY = cmpPosition->GetPosition2D().Y; + CmpPtr cmpFootprint(GetSimContext(), GetEntityId()); + CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); + + RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder); +} + +void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder) +{ + while (index >= m_Path.size()) + { + std::vector tmp; + m_Path.push_back(tmp); + } + m_Path[index].clear(); + + while (index >= m_VisibilitySegments.size()) + { + std::deque tmp; + m_VisibilitySegments.push_back(tmp); + } + m_VisibilitySegments[index].clear(); + + entity_pos_t pathStartX; + entity_pos_t pathStartY; + + if (index == 0) + { + pathStartX = cmpPosition->GetPosition2D().X; + pathStartY = cmpPosition->GetPosition2D().Y; + } + else + { + pathStartX = m_RallyPoints[index-1].X; + pathStartY = m_RallyPoints[index-1].Y; + } // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a - // list of waypoints (i.e. a Path) from the building to the goal, where each waypoint is centered - // at a tile. We'll have to do some post-processing on the path to get it smooth. + // list of waypoints (i.e. a Path) from the building/previous rally point to the goal, where each + // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth. Path path; std::vector& waypoints = path.m_Waypoints; - Goal goal = { Goal::POINT, m_RallyPoint.X, m_RallyPoint.Y }; - cmpPathFinder->ComputePath( + Goal goal = { Goal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y }; + cmpPathfinder->ComputePath( pathStartX, pathStartY, goal, - cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass), - cmpPathFinder->GetCostClass(m_LineCostClass), + cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), + cmpPathfinder->GetCostClass(m_LineCostClass), path ); + // Check if we got a path back; if not we probably have two markers less than one tile apart. if (path.m_Waypoints.size() < 2) - return; // not likely to happen, but can't hurt to check + { + m_Path[index].push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat())); + m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat())); + return; + } // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot. @@ -525,46 +635,55 @@ void CCmpRallyPointRenderer::RecomputeRallyPointPath() Waypoint& lastWaypoint = waypoints.back(); if (lastWaypoint.x != goal.x || lastWaypoint.z != goal.z) - m_Path.push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat())); + m_Path[index].push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat())); // add the rest of the waypoints for (size_t i = 0; i < waypoints.size(); ++i) - m_Path.push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat())); + m_Path[index].push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat())); + + // add the start position + m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat())); // ------------------------------------------------------------------------------------------- // post-processing // Linearize the path; // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path - // goes from the marker to this entity and we want to keep the point at the marker's exact position, loop backwards through the - // waypoints so that the marker waypoint is maintained. + // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position, + // loop backwards through the waypoints so that the marker waypoint is maintained. // TODO: see if we can do this at the same time as the waypoint -> coord conversion above - for(size_t i = m_Path.size() - 1; i > 0; --i) - m_Path[i] = (m_Path[i] + m_Path[i-1]) / 2.0f; + for(size_t i = m_Path[index].size() - 2; i > 0; --i) + m_Path[index][i] = (m_Path[index][i] + m_Path[index][i-1]) / 2.0f; - // if there's a footprint, remove any points returned by the pathfinder that may be on obstructed footprint tiles - if (cmpFootprint) - FixFootprintWaypoints(m_Path, cmpPosition, cmpFootprint); + // if there's a footprint and this path starts from this entity, remove any points returned by the pathfinder that may be on obstructed footprint tiles + if (index == 0 && cmpFootprint) + FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint); // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles // (prevents segments that are too long to properly stick to the terrain) - ReduceSegmentsByVisibility(m_Path, 6); + ReduceSegmentsByVisibility(m_Path[index], 6); //// /////////////////////////////////////////////// if (m_EnableDebugNodeOverlay) - m_DebugNodeOverlays.clear(); - + { + while (index >= m_DebugNodeOverlays.size()) + { + std::vector tmp; + m_DebugNodeOverlays.push_back(tmp); + } + m_DebugNodeOverlays[index].clear(); + } if (m_EnableDebugNodeOverlay && m_SmoothPath) { // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the // terrain so we can still see them after the interpolated points are added) - for (size_t j = 0; j < m_Path.size(); ++j) + for (size_t j = 0; j < m_Path[index].size(); ++j) { SOverlayLine overlayLine; overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f); overlayLine.m_Thickness = 2; - SimRender::ConstructSquareOnGround(GetSimContext(), m_Path[j].X, m_Path[j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true); - m_DebugNodeOverlays.push_back(overlayLine); + SimRender::ConstructSquareOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true); + m_DebugNodeOverlays[index].push_back(overlayLine); } } //// ////////////////////////////////////////////// @@ -573,38 +692,51 @@ void CCmpRallyPointRenderer::RecomputeRallyPointPath() // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to // generate to be able to deal with local terrain height changes. - SimRender::InterpolatePointsRNS(m_Path, false, 0, 8); // no offset, keep line at its exact path + SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 8); // no offset, keep line at its exact path // ------------------------------------------------------------------------------------------- - + // find which point is the last visible point before going into the SoD, so we have a point to compare to on the next turn - GetVisibilitySegments(m_VisibilitySegments); + GetVisibilitySegments(m_VisibilitySegments[index], index); // build overlay lines for the new path - ConstructOverlayLines(); + ConstructOverlayLines(index); +} + +void CCmpRallyPointRenderer::ConstructAllOverlayLines() +{ + m_TexturedOverlayLines.clear(); + + for (size_t i = 0; i < m_Path.size(); ++i) + ConstructOverlayLines(i); } -void CCmpRallyPointRenderer::ConstructOverlayLines() +void CCmpRallyPointRenderer::ConstructOverlayLines(size_t index) { // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every // pass (which is only sensible). - m_TexturedOverlayLines.clear(); + while (index >= m_TexturedOverlayLines.size()) + { + std::vector tmp; + m_TexturedOverlayLines.push_back(tmp); + } + m_TexturedOverlayLines[index].clear(); - if (m_Path.size() < 2) + if (m_Path[index].size() < 2) return; CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it) - for (std::deque::const_iterator it = m_VisibilitySegments.begin(); it != m_VisibilitySegments.end(); ++it) + for (std::deque::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it) { const SVisibilitySegment& segment = (*it); if (segment.m_Visible) { // does this segment border on the building or rally point flag on either side? - bool bordersBuilding = (segment.m_EndIndex == m_Path.size() - 1); + bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1); bool bordersFlag = (segment.m_StartIndex == 0); // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx @@ -626,18 +758,18 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() ENSURE(segment.m_EndIndex > segment.m_StartIndex); for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here { - overlayLine.m_Coords.push_back(m_Path[j].X); - overlayLine.m_Coords.push_back(m_Path[j].Y); + overlayLine.m_Coords.push_back(m_Path[index][j].X); + overlayLine.m_Coords.push_back(m_Path[index][j].Y); } - m_TexturedOverlayLines.push_back(overlayLine); + m_TexturedOverlayLines[index].push_back(overlayLine); } else { // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list std::vector straightLine; - straightLine.push_back(m_Path[segment.m_StartIndex]); - straightLine.push_back(m_Path[segment.m_EndIndex]); + straightLine.push_back(m_Path[index][segment.m_StartIndex]); + straightLine.push_back(m_Path[index][segment.m_EndIndex]); // We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them @@ -650,7 +782,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() float clearSize = maxClearSize; float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length - float distance = (m_Path[segment.m_StartIndex] - m_Path[segment.m_EndIndex]).Length(); // straight-line distance between the points + float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is. @@ -702,7 +834,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y); } - m_TexturedOverlayLines.push_back(dashOverlay); + m_TexturedOverlayLines[index].push_back(dashOverlay); } } @@ -711,13 +843,18 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() //// ////////////////////////////////////////////// if (m_EnableDebugNodeOverlay) { - for (size_t j = 0; j < m_Path.size(); ++j) + while (index >= m_DebugNodeOverlays.size()) + { + std::vector tmp; + m_DebugNodeOverlays.push_back(tmp); + } + for (size_t j = 0; j < m_Path[index].size(); ++j) { SOverlayLine overlayLine; overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f); overlayLine.m_Thickness = 1; - SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[j].X, m_Path[j].Y, 0.075f, overlayLine, true); - m_DebugNodeOverlays.push_back(overlayLine); + SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.075f, overlayLine, true); + m_DebugNodeOverlays[index].push_back(overlayLine); } } //// ////////////////////////////////////////////// @@ -731,17 +868,32 @@ void CCmpRallyPointRenderer::UpdateOverlayLines() return; // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones - std::deque newVisibilitySegments; - GetVisibilitySegments(newVisibilitySegments); + std::deque > newVisibilitySegments; + for (size_t i = 0; i < m_Path.size(); ++i) + { + std::deque tmp; + newVisibilitySegments.push_back(tmp); + GetVisibilitySegments(newVisibilitySegments[i], i); + } - // compare the two indices vectors; as soon as an element is different (and provided the full path hasn't changed), then the SoD - // has changed and we should recreate the overlay lines - if (m_VisibilitySegments != newVisibilitySegments) + // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that. + if (m_VisibilitySegments.size() != newVisibilitySegments.size()) { - // the visibility segments have changed, so we want to reconstruct the overlay lines to match. Note that the path itself doesn't - // change, only the overlay lines we construct from them. m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time - ConstructOverlayLines(); + ConstructAllOverlayLines(); + } + else + { + for (size_t i = 0; i < m_VisibilitySegments.size(); ++i) + { + if (m_VisibilitySegments[i] != newVisibilitySegments[i]) + { + // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't + // change, only the overlay lines we construct from it. + m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time + ConstructOverlayLines(i); + } + } } } @@ -830,30 +982,6 @@ void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector& coord } } -void CCmpRallyPointRenderer::FixInvisibleWaypoints(std::vector& coords) -{ - CmpPtr cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); - - player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); - CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); - - for(std::vector::iterator it = coords.begin(); it != coords.end();) - { - int i = (fixed::FromFloat(it->X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); - int j = (fixed::FromFloat(it->Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); - - bool explored = losQuerier.IsExplored(i, j); - if (!explored) - { - it = coords.erase(it); - } - else - { - ++it; - } - } -} - void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks, bool floating) { CmpPtr cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); @@ -948,11 +1076,11 @@ void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector& coords.swap(newCoords); } -void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque& out) +void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque& out, size_t index) { out.clear(); - if (m_Path.size() < 2) + if (m_Path[index].size() < 2) return; CmpPtr cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); @@ -964,16 +1092,16 @@ void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque