Fixes macOS warnings after adding final keyword to simulation classes in rP26537.
[0ad.git] / source / simulation2 / components / CCmpTerritoryManager.cpp
blob563514b9b17670217f4b97e66822cec7c527c703
1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "simulation2/system/Component.h"
21 #include "ICmpTerritoryManager.h"
23 #include "graphics/Overlay.h"
24 #include "graphics/Terrain.h"
25 #include "graphics/TextureManager.h"
26 #include "graphics/TerritoryBoundary.h"
27 #include "maths/MathUtil.h"
28 #include "ps/Profile.h"
29 #include "ps/XML/Xeromyces.h"
30 #include "renderer/Renderer.h"
31 #include "renderer/Scene.h"
32 #include "renderer/TerrainOverlay.h"
33 #include "simulation2/MessageTypes.h"
34 #include "simulation2/components/ICmpOwnership.h"
35 #include "simulation2/components/ICmpPathfinder.h"
36 #include "simulation2/components/ICmpPlayer.h"
37 #include "simulation2/components/ICmpPlayerManager.h"
38 #include "simulation2/components/ICmpPosition.h"
39 #include "simulation2/components/ICmpTerritoryDecayManager.h"
40 #include "simulation2/components/ICmpTerritoryInfluence.h"
41 #include "simulation2/helpers/Grid.h"
42 #include "simulation2/helpers/Render.h"
44 #include <queue>
46 class CCmpTerritoryManager;
48 class TerritoryOverlay final : public TerrainTextureOverlay
50 NONCOPYABLE(TerritoryOverlay);
51 public:
52 CCmpTerritoryManager& m_TerritoryManager;
54 TerritoryOverlay(CCmpTerritoryManager& manager);
55 void BuildTextureRGBA(u8* data, size_t w, size_t h) override;
58 class CCmpTerritoryManager : public ICmpTerritoryManager
60 public:
61 static void ClassInit(CComponentManager& componentManager)
63 componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
64 componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
65 componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
66 componentManager.SubscribeGloballyToMessageType(MT_ValueModification);
67 componentManager.SubscribeToMessageType(MT_ObstructionMapShapeChanged);
68 componentManager.SubscribeToMessageType(MT_TerrainChanged);
69 componentManager.SubscribeToMessageType(MT_WaterChanged);
70 componentManager.SubscribeToMessageType(MT_Update);
71 componentManager.SubscribeToMessageType(MT_Interpolate);
72 componentManager.SubscribeToMessageType(MT_RenderSubmit);
75 DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
77 static std::string GetSchema()
79 return "<a:component type='system'/><empty/>";
82 u8 m_ImpassableCost;
83 float m_BorderThickness;
84 float m_BorderSeparation;
86 // Player ID in bits 0-4 (TERRITORY_PLAYER_MASK)
87 // connected flag in bit 5 (TERRITORY_CONNECTED_MASK)
88 // blinking flag in bit 6 (TERRITORY_BLINKING_MASK)
89 // processed flag in bit 7 (TERRITORY_PROCESSED_MASK)
90 Grid<u8>* m_Territories;
92 std::vector<u16> m_TerritoryCellCounts;
93 u16 m_TerritoryTotalPassableCellCount;
95 // Saves the cost per tile (to stop territory on impassable tiles)
96 Grid<u8>* m_CostGrid;
98 // Set to true when territories change; will send a TerritoriesChanged message
99 // during the Update phase
100 bool m_TriggerEvent;
102 struct SBoundaryLine
104 bool blinking;
105 player_id_t owner;
106 CColor color;
107 SOverlayTexturedLine overlay;
110 std::vector<SBoundaryLine> m_BoundaryLines;
111 bool m_BoundaryLinesDirty;
113 double m_AnimTime; // time since start of rendering, in seconds
115 TerritoryOverlay* m_DebugOverlay;
117 bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines?
118 std::vector<SOverlayLine> m_DebugBoundaryLineNodes;
120 void Init(const CParamNode& UNUSED(paramNode)) override
122 m_Territories = NULL;
123 m_CostGrid = NULL;
124 m_DebugOverlay = NULL;
125 // m_DebugOverlay = new TerritoryOverlay(*this);
126 m_BoundaryLinesDirty = true;
127 m_TriggerEvent = true;
128 m_EnableLineDebugOverlays = false;
129 m_DirtyID = 1;
130 m_DirtyBlinkingID = 1;
131 m_Visible = true;
132 m_ColorChanged = false;
134 m_AnimTime = 0.0;
136 m_TerritoryTotalPassableCellCount = 0;
138 // Register Relax NG validator
139 CXeromyces::AddValidator(g_VFS, "territorymanager", "simulation/data/territorymanager.rng");
141 CParamNode externalParamNode;
142 CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml", "territorymanager");
144 int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt();
145 ENSURE(0 <= impassableCost && impassableCost <= 255);
146 m_ImpassableCost = (u8)impassableCost;
147 m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat();
148 m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat();
151 void Deinit() override
153 SAFE_DELETE(m_Territories);
154 SAFE_DELETE(m_CostGrid);
155 SAFE_DELETE(m_DebugOverlay);
158 void Serialize(ISerializer& serialize) override
160 // Territory state can be recomputed as required, so we don't need to serialize any of it.
161 serialize.Bool("trigger event", m_TriggerEvent);
164 void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
166 Init(paramNode);
167 deserialize.Bool("trigger event", m_TriggerEvent);
170 void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
172 switch (msg.GetType())
174 case MT_OwnershipChanged:
176 const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
177 MakeDirtyIfRelevantEntity(msgData.entity);
178 break;
180 case MT_PlayerColorChanged:
182 MakeDirty();
183 break;
185 case MT_PositionChanged:
187 const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
188 MakeDirtyIfRelevantEntity(msgData.entity);
189 break;
191 case MT_ValueModification:
193 const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
194 if (msgData.component == L"TerritoryInfluence")
195 MakeDirty();
196 break;
198 case MT_ObstructionMapShapeChanged:
199 case MT_TerrainChanged:
200 case MT_WaterChanged:
202 // also recalculate the cost grid to support atlas changes
203 SAFE_DELETE(m_CostGrid);
204 MakeDirty();
205 break;
207 case MT_Update:
209 if (m_TriggerEvent)
211 m_TriggerEvent = false;
212 GetSimContext().GetComponentManager().BroadcastMessage(CMessageTerritoriesChanged());
214 break;
216 case MT_Interpolate:
218 const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
219 Interpolate(msgData.deltaSimTime, msgData.offset);
220 break;
222 case MT_RenderSubmit:
224 const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
225 RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
226 break;
231 // Check whether the entity is either a settlement or territory influence;
232 // ignore any others
233 void MakeDirtyIfRelevantEntity(entity_id_t ent)
235 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
236 if (cmpTerritoryInfluence)
237 MakeDirty();
240 const Grid<u8>& GetTerritoryGrid() override
242 CalculateTerritories();
243 ENSURE(m_Territories);
244 return *m_Territories;
247 player_id_t GetOwner(entity_pos_t x, entity_pos_t z) override;
248 std::vector<u32> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) override;
249 bool IsConnected(entity_pos_t x, entity_pos_t z) override;
251 void SetTerritoryBlinking(entity_pos_t x, entity_pos_t z, bool enable) override;
252 bool IsTerritoryBlinking(entity_pos_t x, entity_pos_t z) override;
254 // To support lazy updates of territory render data,
255 // we maintain a DirtyID here and increment it whenever territories change;
256 // if a caller has a lower DirtyID then it needs to be updated.
257 // We also do the same thing for blinking updates using DirtyBlinkingID.
259 size_t m_DirtyID;
260 size_t m_DirtyBlinkingID;
262 bool m_ColorChanged;
264 void MakeDirty()
266 SAFE_DELETE(m_Territories);
267 ++m_DirtyID;
268 m_BoundaryLinesDirty = true;
269 m_TriggerEvent = true;
272 bool NeedUpdateTexture(size_t* dirtyID) override
274 if (*dirtyID == m_DirtyID && !m_ColorChanged)
275 return false;
277 *dirtyID = m_DirtyID;
278 m_ColorChanged = false;
279 return true;
282 bool NeedUpdateAI(size_t* dirtyID, size_t* dirtyBlinkingID) const override
284 if (*dirtyID == m_DirtyID && *dirtyBlinkingID == m_DirtyBlinkingID)
285 return false;
287 *dirtyID = m_DirtyID;
288 *dirtyBlinkingID = m_DirtyBlinkingID;
289 return true;
292 void CalculateCostGrid();
294 void CalculateTerritories();
296 u8 GetTerritoryPercentage(player_id_t player) override;
298 std::vector<STerritoryBoundary> ComputeBoundaries();
300 void UpdateBoundaryLines();
302 void Interpolate(float frameTime, float frameOffset);
304 void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
306 void SetVisibility(bool visible) override
308 m_Visible = visible;
311 void UpdateColors() override;
313 private:
315 bool m_Visible;
318 REGISTER_COMPONENT_TYPE(TerritoryManager)
320 // Tile data type, for easier accessing of coordinates
321 struct Tile
323 Tile(u16 i, u16 j) : x(i), z(j) { }
324 u16 x, z;
327 // Floodfill templates that expand neighbours from a certain source onwards
328 // (posX, posZ) are the coordinates of the currently expanded tile
329 // (nx, nz) are the coordinates of the current neighbour handled
330 // The user of this floodfill should use "continue" on every neighbour that
331 // shouldn't be expanded on its own. (without continue, an infinite loop will happen)
332 # define FLOODFILL(i, j, code)\
333 do {\
334 const int NUM_NEIGHBOURS = 8;\
335 const int NEIGHBOURS_X[NUM_NEIGHBOURS] = {1,-1, 0, 0, 1,-1, 1,-1};\
336 const int NEIGHBOURS_Z[NUM_NEIGHBOURS] = {0, 0, 1,-1, 1,-1,-1, 1};\
337 std::queue<Tile> openTiles;\
338 openTiles.emplace(i, j);\
339 while (!openTiles.empty())\
341 u16 posX = openTiles.front().x;\
342 u16 posZ = openTiles.front().z;\
343 openTiles.pop();\
344 for (int n = 0; n < NUM_NEIGHBOURS; ++n)\
346 u16 nx = posX + NEIGHBOURS_X[n];\
347 u16 nz = posZ + NEIGHBOURS_Z[n];\
348 /* Check the bounds, underflow will cause the values to be big again */\
349 if (nx >= tilesW || nz >= tilesH)\
350 continue;\
351 code\
352 openTiles.emplace(nx, nz);\
356 while (false)
359 * Compute the tile indexes on the grid nearest to a given point
361 static void NearestTerritoryTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
363 entity_pos_t scale = Pathfinding::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE;
364 i = Clamp((x / scale).ToInt_RoundToNegInfinity(), 0, w - 1);
365 j = Clamp((z / scale).ToInt_RoundToNegInfinity(), 0, h - 1);
368 void CCmpTerritoryManager::CalculateCostGrid()
370 if (m_CostGrid)
371 return;
373 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
374 if (!cmpPathfinder)
375 return;
377 pass_class_t passClassTerritory = cmpPathfinder->GetPassabilityClass("default-terrain-only");
378 pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
380 const Grid<NavcellData>& passGrid = cmpPathfinder->GetPassabilityGrid();
382 int tilesW = passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE;
383 int tilesH = passGrid.m_H / NAVCELLS_PER_TERRITORY_TILE;
385 m_CostGrid = new Grid<u8>(tilesW, tilesH);
386 m_TerritoryTotalPassableCellCount = 0;
388 for (int i = 0; i < tilesW; ++i)
390 for (int j = 0; j < tilesH; ++j)
392 NavcellData c = 0;
393 for (u16 di = 0; di < NAVCELLS_PER_TERRITORY_TILE; ++di)
394 for (u16 dj = 0; dj < NAVCELLS_PER_TERRITORY_TILE; ++dj)
395 c |= passGrid.get(
396 i * NAVCELLS_PER_TERRITORY_TILE + di,
397 j * NAVCELLS_PER_TERRITORY_TILE + dj);
398 if (!IS_PASSABLE(c, passClassTerritory))
399 m_CostGrid->set(i, j, m_ImpassableCost);
400 else if (!IS_PASSABLE(c, passClassUnrestricted))
401 m_CostGrid->set(i, j, 255); // off the world; use maximum cost
402 else
404 m_CostGrid->set(i, j, 1);
405 ++m_TerritoryTotalPassableCellCount;
411 void CCmpTerritoryManager::CalculateTerritories()
413 if (m_Territories)
414 return;
416 PROFILE("CalculateTerritories");
418 // If the pathfinder hasn't been loaded (e.g. this is called during map initialisation),
419 // abort the computation (and assume callers can cope with m_Territories == NULL)
420 CalculateCostGrid();
421 if (!m_CostGrid)
422 return;
424 const u16 tilesW = m_CostGrid->m_W;
425 const u16 tilesH = m_CostGrid->m_H;
427 m_Territories = new Grid<u8>(tilesW, tilesH);
429 // Reset territory counts for all players
430 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
431 if (cmpPlayerManager && (size_t)cmpPlayerManager->GetNumPlayers() != m_TerritoryCellCounts.size())
432 m_TerritoryCellCounts.resize(cmpPlayerManager->GetNumPlayers());
433 for (u16& count : m_TerritoryCellCounts)
434 count = 0;
436 // Find all territory influence entities
437 CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
439 // Split influence entities into per-player lists, ignoring any with invalid properties
440 std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
441 for (const CComponentManager::InterfacePair& pair : influences)
443 entity_id_t ent = pair.first;
445 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
446 if (!cmpOwnership)
447 continue;
449 // Ignore Gaia and unassigned or players we can't represent
450 player_id_t owner = cmpOwnership->GetOwner();
451 if (owner <= 0 || owner > TERRITORY_PLAYER_MASK)
452 continue;
454 influenceEntities[owner].push_back(ent);
457 // Store the overall best weight for comparison
458 Grid<u32> bestWeightGrid(tilesW, tilesH);
459 // store the root influences to mark territory as connected
460 std::vector<entity_id_t> rootInfluenceEntities;
462 for (const std::pair<const player_id_t, std::vector<entity_id_t>>& pair : influenceEntities)
464 // entityGrid stores the weight for a single entity, and is reset per entity
465 Grid<u32> entityGrid(tilesW, tilesH);
466 // playerGrid stores the combined weight of all entities for this player
467 Grid<u32> playerGrid(tilesW, tilesH);
469 u8 owner = static_cast<u8>(pair.first);
470 const std::vector<entity_id_t>& ents = pair.second;
471 // With 2^16 entities, we're safe against overflows as the weight is also limited to 2^16
472 ENSURE(ents.size() < 1 << 16);
473 // Compute the influence map of the current entity, then add it to the player grid
474 for (entity_id_t ent : ents)
476 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
477 if (!cmpPosition || !cmpPosition->IsInWorld())
478 continue;
480 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
481 u32 weight = cmpTerritoryInfluence->GetWeight();
482 u32 radius = cmpTerritoryInfluence->GetRadius();
483 if (weight == 0 || radius == 0)
484 continue;
485 u32 falloff = weight * (Pathfinding::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE).ToInt_RoundToNegInfinity() / radius;
487 CFixedVector2D pos = cmpPosition->GetPosition2D();
488 u16 i, j;
489 NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
491 if (cmpTerritoryInfluence->IsRoot())
492 rootInfluenceEntities.push_back(ent);
494 // Initialise the tile under the entity
495 entityGrid.set(i, j, weight);
496 if (weight > bestWeightGrid.get(i, j))
498 bestWeightGrid.set(i, j, weight);
499 m_Territories->set(i, j, owner);
502 // Expand influences outwards
503 FLOODFILL(i, j,
504 u32 dg = falloff * m_CostGrid->get(nx, nz);
506 // diagonal neighbour -> multiply with approx sqrt(2)
507 if (nx != posX && nz != posZ)
508 dg = (dg * 362) / 256;
510 // Don't expand if new cost is not better than previous value for that tile
511 // (arranged to avoid underflow if entityGrid.get(x, z) < dg)
512 if (entityGrid.get(posX, posZ) <= entityGrid.get(nx, nz) + dg)
513 continue;
515 // weight of this tile = weight of predecessor - falloff from predecessor
516 u32 newWeight = entityGrid.get(posX, posZ) - dg;
517 u32 totalWeight = playerGrid.get(nx, nz) - entityGrid.get(nx, nz) + newWeight;
518 playerGrid.set(nx, nz, totalWeight);
519 entityGrid.set(nx, nz, newWeight);
520 // if this weight is better than the best thus far, set the owner
521 if (totalWeight > bestWeightGrid.get(nx, nz))
523 bestWeightGrid.set(nx, nz, totalWeight);
524 m_Territories->set(nx, nz, owner);
528 entityGrid.reset();
532 // Detect territories connected to a 'root' influence (typically a civ center)
533 // belonging to their player, and mark them with the connected flag
534 for (entity_id_t ent : rootInfluenceEntities)
536 // (These components must be valid else the entities wouldn't be added to this list)
537 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
538 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
540 CFixedVector2D pos = cmpPosition->GetPosition2D();
541 u16 i, j;
542 NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
544 u8 owner = (u8)cmpOwnership->GetOwner();
546 if (m_Territories->get(i, j) != owner)
547 continue;
549 m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK);
551 FLOODFILL(i, j,
552 // Don't expand non-owner tiles, or tiles that already have a connected mask
553 if (m_Territories->get(nx, nz) != owner)
554 continue;
555 m_Territories->set(nx, nz, owner | TERRITORY_CONNECTED_MASK);
556 if (m_CostGrid->get(nx, nz) < m_ImpassableCost)
557 ++m_TerritoryCellCounts[owner];
561 // Then recomputes the blinking tiles
562 CmpPtr<ICmpTerritoryDecayManager> cmpTerritoryDecayManager(GetSystemEntity());
563 if (cmpTerritoryDecayManager)
565 size_t dirtyBlinkingID = m_DirtyBlinkingID;
566 cmpTerritoryDecayManager->SetBlinkingEntities();
567 m_DirtyBlinkingID = dirtyBlinkingID;
571 std::vector<STerritoryBoundary> CCmpTerritoryManager::ComputeBoundaries()
573 PROFILE("ComputeBoundaries");
575 CalculateTerritories();
576 ENSURE(m_Territories);
578 return CTerritoryBoundaryCalculator::ComputeBoundaries(m_Territories);
581 u8 CCmpTerritoryManager::GetTerritoryPercentage(player_id_t player)
583 if (player <= 0 || static_cast<size_t>(player) >= m_TerritoryCellCounts.size())
584 return 0;
586 CalculateTerritories();
588 // Territories may have been recalculated, check whether player is still there.
589 if (m_TerritoryTotalPassableCellCount == 0 || static_cast<size_t>(player) >= m_TerritoryCellCounts.size())
590 return 0;
592 u8 percentage = (m_TerritoryCellCounts[player] * 100) / m_TerritoryTotalPassableCellCount;
593 ENSURE(percentage <= 100);
594 return percentage;
597 void CCmpTerritoryManager::UpdateBoundaryLines()
599 PROFILE("update boundary lines");
601 m_BoundaryLines.clear();
602 m_DebugBoundaryLineNodes.clear();
604 if (!CRenderer::IsInitialised())
605 return;
607 std::vector<STerritoryBoundary> boundaries = ComputeBoundaries();
609 CTextureProperties texturePropsBase("art/textures/misc/territory_border.png");
610 texturePropsBase.SetAddressMode(
611 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
612 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
613 texturePropsBase.SetAnisotropicFilter(true);
614 CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
616 CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png");
617 texturePropsMask.SetAddressMode(
618 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
619 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
620 texturePropsMask.SetAnisotropicFilter(true);
621 CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
623 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
624 if (!cmpPlayerManager)
625 return;
627 for (size_t i = 0; i < boundaries.size(); ++i)
629 if (boundaries[i].points.empty())
630 continue;
632 CColor color(1, 0, 1, 1);
633 CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner));
634 if (cmpPlayer)
635 color = cmpPlayer->GetDisplayedColor();
637 m_BoundaryLines.push_back(SBoundaryLine());
638 m_BoundaryLines.back().blinking = boundaries[i].blinking;
639 m_BoundaryLines.back().owner = boundaries[i].owner;
640 m_BoundaryLines.back().color = color;
641 m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext();
642 m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
643 m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
644 m_BoundaryLines.back().overlay.m_Color = color;
645 m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
646 m_BoundaryLines.back().overlay.m_Closed = true;
648 SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
649 SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
651 std::vector<CVector2D>& points = m_BoundaryLines.back().overlay.m_Coords;
652 for (size_t j = 0; j < boundaries[i].points.size(); ++j)
654 points.push_back(boundaries[i].points[j]);
656 if (m_EnableLineDebugOverlays)
658 const size_t numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
659 SOverlayLine overlayNode;
660 if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
661 overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
662 else if (j < numHighlightNodes)
663 overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
664 else
665 overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
667 overlayNode.m_Thickness = 0.1f;
668 SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
669 m_DebugBoundaryLineNodes.push_back(overlayNode);
676 void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset))
678 m_AnimTime += frameTime;
680 if (m_BoundaryLinesDirty)
682 UpdateBoundaryLines();
683 m_BoundaryLinesDirty = false;
686 for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
688 if (m_BoundaryLines[i].blinking)
690 CColor c = m_BoundaryLines[i].color;
691 c.a *= 0.2f + 0.8f * fabsf((float)cos(m_AnimTime * M_PI)); // TODO: should let artists tweak this
692 m_BoundaryLines[i].overlay.m_Color = c;
697 void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
699 if (!m_Visible)
700 return;
702 for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
704 if (culling && !m_BoundaryLines[i].overlay.IsVisibleInFrustum(frustum))
705 continue;
706 collector.Submit(&m_BoundaryLines[i].overlay);
709 for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
710 collector.Submit(&m_DebugBoundaryLineNodes[i]);
714 player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
716 u16 i, j;
717 if (!m_Territories)
719 CalculateTerritories();
720 if (!m_Territories)
721 return 0;
724 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
725 return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
728 std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
730 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
731 if (!cmpPlayerManager)
732 return std::vector<u32>();
734 std::vector<u32> ret(cmpPlayerManager->GetNumPlayers(), 0);
735 CalculateTerritories();
736 if (!m_Territories)
737 return ret;
739 u16 i, j;
740 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
742 // calculate the neighbours
743 player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
745 u16 tilesW = m_Territories->m_W;
746 u16 tilesH = m_Territories->m_H;
748 // use a flood-fill algorithm that fills up to the borders and remembers the owners
749 Grid<bool> markerGrid(tilesW, tilesH);
750 markerGrid.set(i, j, true);
752 FLOODFILL(i, j,
753 if (markerGrid.get(nx, nz))
754 continue;
755 // mark the tile as visited in any case
756 markerGrid.set(nx, nz, true);
757 int owner = m_Territories->get(nx, nz) & TERRITORY_PLAYER_MASK;
758 if (owner != thisOwner)
760 if (owner == 0 || !filterConnected || (m_Territories->get(nx, nz) & TERRITORY_CONNECTED_MASK) != 0)
761 ret[owner]++; // add player to the neighbour list when requested
762 continue; // don't expand non-owner tiles further
766 return ret;
769 bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
771 u16 i, j;
772 CalculateTerritories();
773 if (!m_Territories)
774 return false;
776 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
777 return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0;
780 void CCmpTerritoryManager::SetTerritoryBlinking(entity_pos_t x, entity_pos_t z, bool enable)
782 CalculateTerritories();
783 if (!m_Territories)
784 return;
786 u16 i, j;
787 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
789 u16 tilesW = m_Territories->m_W;
790 u16 tilesH = m_Territories->m_H;
792 player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
794 FLOODFILL(i, j,
795 u8 bitmask = m_Territories->get(nx, nz);
796 if ((bitmask & TERRITORY_PLAYER_MASK) != thisOwner)
797 continue;
798 u8 blinking = bitmask & TERRITORY_BLINKING_MASK;
799 if (enable && !blinking)
800 m_Territories->set(nx, nz, bitmask | TERRITORY_BLINKING_MASK);
801 else if (!enable && blinking)
802 m_Territories->set(nx, nz, bitmask & ~TERRITORY_BLINKING_MASK);
803 else
804 continue;
806 ++m_DirtyBlinkingID;
807 m_BoundaryLinesDirty = true;
810 bool CCmpTerritoryManager::IsTerritoryBlinking(entity_pos_t x, entity_pos_t z)
812 CalculateTerritories();
813 if (!m_Territories)
814 return false;
816 u16 i, j;
817 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
818 return (m_Territories->get(i, j) & TERRITORY_BLINKING_MASK) != 0;
821 void CCmpTerritoryManager::UpdateColors()
823 m_ColorChanged = true;
825 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
826 if (!cmpPlayerManager)
827 return;
829 for (SBoundaryLine& boundaryLine : m_BoundaryLines)
831 CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaryLine.owner));
832 if (!cmpPlayer)
833 continue;
835 boundaryLine.color = cmpPlayer->GetDisplayedColor();
836 boundaryLine.overlay.m_Color = boundaryLine.color;
840 TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) :
841 TerrainTextureOverlay((float)Pathfinding::NAVCELLS_PER_TERRAIN_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE),
842 m_TerritoryManager(manager)
845 void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h)
847 for (size_t j = 0; j < h; ++j)
849 for (size_t i = 0; i < w; ++i)
851 SColor4ub color;
852 u8 id = (m_TerritoryManager.m_Territories->get((int)i, (int)j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
853 color = GetColor(id, 64);
854 *data++ = color.R;
855 *data++ = color.G;
856 *data++ = color.B;
857 *data++ = color.A;
862 #undef FLOODFILL