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"
46 class CCmpTerritoryManager
;
48 class TerritoryOverlay final
: public TerrainTextureOverlay
50 NONCOPYABLE(TerritoryOverlay
);
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
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/>";
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)
98 // Set to true when territories change; will send a TerritoriesChanged message
99 // during the Update phase
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
;
124 m_DebugOverlay
= NULL
;
125 // m_DebugOverlay = new TerritoryOverlay(*this);
126 m_BoundaryLinesDirty
= true;
127 m_TriggerEvent
= true;
128 m_EnableLineDebugOverlays
= false;
130 m_DirtyBlinkingID
= 1;
132 m_ColorChanged
= false;
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
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
);
180 case MT_PlayerColorChanged
:
185 case MT_PositionChanged
:
187 const CMessagePositionChanged
& msgData
= static_cast<const CMessagePositionChanged
&> (msg
);
188 MakeDirtyIfRelevantEntity(msgData
.entity
);
191 case MT_ValueModification
:
193 const CMessageValueModification
& msgData
= static_cast<const CMessageValueModification
&> (msg
);
194 if (msgData
.component
== L
"TerritoryInfluence")
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
);
211 m_TriggerEvent
= false;
212 GetSimContext().GetComponentManager().BroadcastMessage(CMessageTerritoriesChanged());
218 const CMessageInterpolate
& msgData
= static_cast<const CMessageInterpolate
&> (msg
);
219 Interpolate(msgData
.deltaSimTime
, msgData
.offset
);
222 case MT_RenderSubmit
:
224 const CMessageRenderSubmit
& msgData
= static_cast<const CMessageRenderSubmit
&> (msg
);
225 RenderSubmit(msgData
.collector
, msgData
.frustum
, msgData
.culling
);
231 // Check whether the entity is either a settlement or territory influence;
233 void MakeDirtyIfRelevantEntity(entity_id_t ent
)
235 CmpPtr
<ICmpTerritoryInfluence
> cmpTerritoryInfluence(GetSimContext(), ent
);
236 if (cmpTerritoryInfluence
)
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.
260 size_t m_DirtyBlinkingID
;
266 SAFE_DELETE(m_Territories
);
268 m_BoundaryLinesDirty
= true;
269 m_TriggerEvent
= true;
272 bool NeedUpdateTexture(size_t* dirtyID
) override
274 if (*dirtyID
== m_DirtyID
&& !m_ColorChanged
)
277 *dirtyID
= m_DirtyID
;
278 m_ColorChanged
= false;
282 bool NeedUpdateAI(size_t* dirtyID
, size_t* dirtyBlinkingID
) const override
284 if (*dirtyID
== m_DirtyID
&& *dirtyBlinkingID
== m_DirtyBlinkingID
)
287 *dirtyID
= m_DirtyID
;
288 *dirtyBlinkingID
= m_DirtyBlinkingID
;
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
311 void UpdateColors() override
;
318 REGISTER_COMPONENT_TYPE(TerritoryManager
)
320 // Tile data type, for easier accessing of coordinates
323 Tile(u16 i
, u16 j
) : x(i
), z(j
) { }
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)\
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;\
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)\
352 openTiles.emplace(nx, nz);\
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()
373 CmpPtr
<ICmpPathfinder
> cmpPathfinder(GetSystemEntity());
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
)
393 for (u16 di
= 0; di
< NAVCELLS_PER_TERRITORY_TILE
; ++di
)
394 for (u16 dj
= 0; dj
< NAVCELLS_PER_TERRITORY_TILE
; ++dj
)
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
404 m_CostGrid
->set(i
, j
, 1);
405 ++m_TerritoryTotalPassableCellCount
;
411 void CCmpTerritoryManager::CalculateTerritories()
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)
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
)
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
);
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
)
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())
480 CmpPtr
<ICmpTerritoryInfluence
> cmpTerritoryInfluence(GetSimContext(), ent
);
481 u32 weight
= cmpTerritoryInfluence
->GetWeight();
482 u32 radius
= cmpTerritoryInfluence
->GetRadius();
483 if (weight
== 0 || radius
== 0)
485 u32 falloff
= weight
* (Pathfinding::NAVCELL_SIZE
* NAVCELLS_PER_TERRITORY_TILE
).ToInt_RoundToNegInfinity() / radius
;
487 CFixedVector2D pos
= cmpPosition
->GetPosition2D();
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
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
)
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
);
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();
542 NearestTerritoryTile(pos
.X
, pos
.Y
, i
, j
, tilesW
, tilesH
);
544 u8 owner
= (u8
)cmpOwnership
->GetOwner();
546 if (m_Territories
->get(i
, j
) != owner
)
549 m_Territories
->set(i
, j
, owner
| TERRITORY_CONNECTED_MASK
);
552 // Don't expand non-owner tiles, or tiles that already have a connected mask
553 if (m_Territories
->get(nx
, nz
) != owner
)
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())
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())
592 u8 percentage
= (m_TerritoryCellCounts
[player
] * 100) / m_TerritoryTotalPassableCellCount
;
593 ENSURE(percentage
<= 100);
597 void CCmpTerritoryManager::UpdateBoundaryLines()
599 PROFILE("update boundary lines");
601 m_BoundaryLines
.clear();
602 m_DebugBoundaryLineNodes
.clear();
604 if (!CRenderer::IsInitialised())
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
)
627 for (size_t i
= 0; i
< boundaries
.size(); ++i
)
629 if (boundaries
[i
].points
.empty())
632 CColor
color(1, 0, 1, 1);
633 CmpPtr
<ICmpPlayer
> cmpPlayer(GetSimContext(), cmpPlayerManager
->GetPlayerByID(boundaries
[i
].owner
));
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
);
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
)
702 for (size_t i
= 0; i
< m_BoundaryLines
.size(); ++i
)
704 if (culling
&& !m_BoundaryLines
[i
].overlay
.IsVisibleInFrustum(frustum
))
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
)
719 CalculateTerritories();
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();
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);
753 if (markerGrid
.get(nx
, nz
))
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
769 bool CCmpTerritoryManager::IsConnected(entity_pos_t x
, entity_pos_t z
)
772 CalculateTerritories();
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();
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
;
795 u8 bitmask
= m_Territories
->get(nx
, nz
);
796 if ((bitmask
& TERRITORY_PLAYER_MASK
) != thisOwner
)
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
);
807 m_BoundaryLinesDirty
= true;
810 bool CCmpTerritoryManager::IsTerritoryBlinking(entity_pos_t x
, entity_pos_t z
)
812 CalculateTerritories();
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
)
829 for (SBoundaryLine
& boundaryLine
: m_BoundaryLines
)
831 CmpPtr
<ICmpPlayer
> cmpPlayer(GetSimContext(), cmpPlayerManager
->GetPlayerByID(boundaryLine
.owner
));
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
)
852 u8 id
= (m_TerritoryManager
.m_Territories
->get((int)i
, (int)j
) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK
);
853 color
= GetColor(id
, 64);