1 /* Copyright (C) 2018 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
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.
8  *
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.
13  *
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/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "simulation2/system/Component.h"
21 #include "ICmpTerritoryManager.h"
22 
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"
43 
44 #include <queue>
45 
46 class CCmpTerritoryManager;
47 
48 class TerritoryOverlay : public TerrainTextureOverlay
49 {
50 	NONCOPYABLE(TerritoryOverlay);
51 public:
52 	CCmpTerritoryManager& m_TerritoryManager;
53 
54 	TerritoryOverlay(CCmpTerritoryManager& manager);
55 	virtual void BuildTextureRGBA(u8* data, size_t w, size_t h);
56 };
57 
58 class CCmpTerritoryManager : public ICmpTerritoryManager
59 {
60 public:
ClassInit(CComponentManager & componentManager)61 	static void ClassInit(CComponentManager& componentManager)
62 	{
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);
73 	}
74 
DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)75 	DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
76 
77 	static std::string GetSchema()
78 	{
79 		return "<a:component type='system'/><empty/>";
80 	}
81 
82 	u8 m_ImpassableCost;
83 	float m_BorderThickness;
84 	float m_BorderSeparation;
85 
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;
91 
92 	std::vector<u16> m_TerritoryCellCounts;
93 	u16 m_TerritoryTotalPassableCellCount;
94 
95 	// Saves the cost per tile (to stop territory on impassable tiles)
96 	Grid<u8>* m_CostGrid;
97 
98 	// Set to true when territories change; will send a TerritoriesChanged message
99 	// during the Update phase
100 	bool m_TriggerEvent;
101 
102 	struct SBoundaryLine
103 	{
104 		bool blinking;
105 		player_id_t owner;
106 		CColor color;
107 		SOverlayTexturedLine overlay;
108 	};
109 
110 	std::vector<SBoundaryLine> m_BoundaryLines;
111 	bool m_BoundaryLinesDirty;
112 
113 	double m_AnimTime; // time since start of rendering, in seconds
114 
115 	TerritoryOverlay* m_DebugOverlay;
116 
117 	bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines?
118 	std::vector<SOverlayLine> m_DebugBoundaryLineNodes;
119 
Init(const CParamNode & UNUSED (paramNode))120 	virtual void Init(const CParamNode& UNUSED(paramNode))
121 	{
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;
133 
134 		m_AnimTime = 0.0;
135 
136 		m_TerritoryTotalPassableCellCount = 0;
137 
138 		// Register Relax NG validator
139 		CXeromyces::AddValidator(g_VFS, "territorymanager", "simulation/data/territorymanager.rng");
140 
141 		CParamNode externalParamNode;
142 		CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml", "territorymanager");
143 
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();
149 	}
150 
Deinit()151 	virtual void Deinit()
152 	{
153 		SAFE_DELETE(m_Territories);
154 		SAFE_DELETE(m_CostGrid);
155 		SAFE_DELETE(m_DebugOverlay);
156 	}
157 
Serialize(ISerializer & serialize)158 	virtual void Serialize(ISerializer& serialize)
159 	{
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);
162 	}
163 
Deserialize(const CParamNode & paramNode,IDeserializer & deserialize)164 	virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
165 	{
166 		Init(paramNode);
167 		deserialize.Bool("trigger event", m_TriggerEvent);
168 	}
169 
HandleMessage(const CMessage & msg,bool UNUSED (global))170 	virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
171 	{
172 		switch (msg.GetType())
173 		{
174 		case MT_OwnershipChanged:
175 		{
176 			const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
177 			MakeDirtyIfRelevantEntity(msgData.entity);
178 			break;
179 		}
180 		case MT_PlayerColorChanged:
181 		{
182 			MakeDirty();
183 			break;
184 		}
185 		case MT_PositionChanged:
186 		{
187 			const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
188 			MakeDirtyIfRelevantEntity(msgData.entity);
189 			break;
190 		}
191 		case MT_ValueModification:
192 		{
193 			const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
194 			if (msgData.component == L"TerritoryInfluence")
195 				MakeDirty();
196 			break;
197 		}
198 		case MT_ObstructionMapShapeChanged:
199 		case MT_TerrainChanged:
200 		case MT_WaterChanged:
201 		{
202 			// also recalculate the cost grid to support atlas changes
203 			SAFE_DELETE(m_CostGrid);
204 			MakeDirty();
205 			break;
206 		}
207 		case MT_Update:
208 		{
209 			if (m_TriggerEvent)
210 			{
211 				m_TriggerEvent = false;
212 				CMessageTerritoriesChanged msg;
213 				GetSimContext().GetComponentManager().BroadcastMessage(msg);
214 			}
215 			break;
216 		}
217 		case MT_Interpolate:
218 		{
219 			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
220 			Interpolate(msgData.deltaSimTime, msgData.offset);
221 			break;
222 		}
223 		case MT_RenderSubmit:
224 		{
225 			const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
226 			RenderSubmit(msgData.collector);
227 			break;
228 		}
229 		}
230 	}
231 
232 	// Check whether the entity is either a settlement or territory influence;
233 	// ignore any others
MakeDirtyIfRelevantEntity(entity_id_t ent)234 	void MakeDirtyIfRelevantEntity(entity_id_t ent)
235 	{
236 		CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
237 		if (cmpTerritoryInfluence)
238 			MakeDirty();
239 	}
240 
GetTerritoryGrid()241 	virtual const Grid<u8>& GetTerritoryGrid()
242 	{
243 		CalculateTerritories();
244 		ENSURE(m_Territories);
245 		return *m_Territories;
246 	}
247 
248 	virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z);
249 	virtual std::vector<u32> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected);
250 	virtual bool IsConnected(entity_pos_t x, entity_pos_t z);
251 
252 	virtual void SetTerritoryBlinking(entity_pos_t x, entity_pos_t z, bool enable);
253 	virtual bool IsTerritoryBlinking(entity_pos_t x, entity_pos_t z);
254 
255 	// To support lazy updates of territory render data,
256 	// we maintain a DirtyID here and increment it whenever territories change;
257 	// if a caller has a lower DirtyID then it needs to be updated.
258 	// We also do the same thing for blinking updates using DirtyBlinkingID.
259 
260 	size_t m_DirtyID;
261 	size_t m_DirtyBlinkingID;
262 
263 	bool m_ColorChanged;
264 
MakeDirty()265 	void MakeDirty()
266 	{
267 		SAFE_DELETE(m_Territories);
268 		++m_DirtyID;
269 		m_BoundaryLinesDirty = true;
270 		m_TriggerEvent = true;
271 	}
272 
NeedUpdateTexture(size_t * dirtyID)273 	virtual bool NeedUpdateTexture(size_t* dirtyID)
274 	{
275 		if (*dirtyID == m_DirtyID && !m_ColorChanged)
276 			return false;
277 
278 		*dirtyID = m_DirtyID;
279 		m_ColorChanged = false;
280 		return true;
281 	}
282 
NeedUpdateAI(size_t * dirtyID,size_t * dirtyBlinkingID) const283 	virtual bool NeedUpdateAI(size_t* dirtyID, size_t* dirtyBlinkingID) const
284 	{
285 		if (*dirtyID == m_DirtyID && *dirtyBlinkingID == m_DirtyBlinkingID)
286 			return false;
287 
288 		*dirtyID = m_DirtyID;
289 		*dirtyBlinkingID = m_DirtyBlinkingID;
290 		return true;
291 	}
292 
293 	void CalculateCostGrid();
294 
295 	void CalculateTerritories();
296 
297 	u8 GetTerritoryPercentage(player_id_t player);
298 
299 	std::vector<STerritoryBoundary> ComputeBoundaries();
300 
301 	void UpdateBoundaryLines();
302 
303 	void Interpolate(float frameTime, float frameOffset);
304 
305 	void RenderSubmit(SceneCollector& collector);
306 
SetVisibility(bool visible)307 	void SetVisibility(bool visible)
308 	{
309 		m_Visible = visible;
310 	}
311 
312 	void UpdateColors();
313 
314 private:
315 
316 	bool m_Visible;
317 };
318 
319 REGISTER_COMPONENT_TYPE(TerritoryManager)
320 
321 // Tile data type, for easier accessing of coordinates
322 struct Tile
323 {
TileTile324 	Tile(u16 i, u16 j) : x(i), z(j) { }
325 	u16 x, z;
326 };
327 
328 // Floodfill templates that expand neighbours from a certain source onwards
329 // (x, z) are the coordinates of the currently expanded tile
330 // (nx, nz) are the coordinates of the current neighbour handled
331 // The user of this floodfill should use "continue" on every neighbour that
332 // shouldn't be expanded on its own. (without continue, an infinite loop will happen)
333 # define FLOODFILL(i, j, code)\
334 	do {\
335 		const int NUM_NEIGHBOURS = 8;\
336 		const int NEIGHBOURS_X[NUM_NEIGHBOURS] = {1,-1, 0, 0, 1,-1, 1,-1};\
337 		const int NEIGHBOURS_Z[NUM_NEIGHBOURS] = {0, 0, 1,-1, 1,-1,-1, 1};\
338 		std::queue<Tile> openTiles;\
339 		openTiles.emplace(i, j);\
340 		while (!openTiles.empty())\
341 		{\
342 			u16 x = openTiles.front().x;\
343 			u16 z = openTiles.front().z;\
344 			openTiles.pop();\
345 			for (int n = 0; n < NUM_NEIGHBOURS; ++n)\
346 			{\
347 				u16 nx = x + NEIGHBOURS_X[n];\
348 				u16 nz = z + NEIGHBOURS_Z[n];\
349 				/* Check the bounds, underflow will cause the values to be big again */\
350 				if (nx >= tilesW || nz >= tilesH)\
351 					continue;\
352 				code\
353 				openTiles.emplace(nx, nz);\
354 			}\
355 		}\
356 	}\
357 	while (false)
358 
359 /**
360  * Compute the tile indexes on the grid nearest to a given point
361  */
NearestTerritoryTile(entity_pos_t x,entity_pos_t z,u16 & i,u16 & j,u16 w,u16 h)362 static void NearestTerritoryTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
363 {
364 	entity_pos_t scale = Pathfinding::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE;
365 	i = clamp((x / scale).ToInt_RoundToNegInfinity(), 0, w - 1);
366 	j = clamp((z / scale).ToInt_RoundToNegInfinity(), 0, h - 1);
367 }
368 
CalculateCostGrid()369 void CCmpTerritoryManager::CalculateCostGrid()
370 {
371 	if (m_CostGrid)
372 		return;
373 
374 	CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
375 	if (!cmpPathfinder)
376 		return;
377 
378 	pass_class_t passClassTerritory = cmpPathfinder->GetPassabilityClass("default-terrain-only");
379 	pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
380 
381 	const Grid<NavcellData>& passGrid = cmpPathfinder->GetPassabilityGrid();
382 
383 	int tilesW = passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE;
384 	int tilesH = passGrid.m_H / NAVCELLS_PER_TERRITORY_TILE;
385 
386 	m_CostGrid = new Grid<u8>(tilesW, tilesH);
387 	m_TerritoryTotalPassableCellCount = 0;
388 
389 	for (int i = 0; i < tilesW; ++i)
390 	{
391 		for (int j = 0; j < tilesH; ++j)
392 		{
393 			NavcellData c = 0;
394 			for (u16 di = 0; di < NAVCELLS_PER_TERRITORY_TILE; ++di)
395 				for (u16 dj = 0; dj < NAVCELLS_PER_TERRITORY_TILE; ++dj)
396 					c |= passGrid.get(
397 						i * NAVCELLS_PER_TERRITORY_TILE + di,
398 						j * NAVCELLS_PER_TERRITORY_TILE + dj);
399 			if (!IS_PASSABLE(c, passClassTerritory))
400 				m_CostGrid->set(i, j, m_ImpassableCost);
401 			else if (!IS_PASSABLE(c, passClassUnrestricted))
402 				m_CostGrid->set(i, j, 255); // off the world; use maximum cost
403 			else
404 			{
405 				m_CostGrid->set(i, j, 1);
406 				++m_TerritoryTotalPassableCellCount;
407 			}
408 		}
409 	}
410 }
411 
CalculateTerritories()412 void CCmpTerritoryManager::CalculateTerritories()
413 {
414 	if (m_Territories)
415 		return;
416 
417 	PROFILE("CalculateTerritories");
418 
419 	// If the pathfinder hasn't been loaded (e.g. this is called during map initialisation),
420 	// abort the computation (and assume callers can cope with m_Territories == NULL)
421 	CalculateCostGrid();
422 	if (!m_CostGrid)
423 		return;
424 
425 	const u16 tilesW = m_CostGrid->m_W;
426 	const u16 tilesH = m_CostGrid->m_H;
427 
428 	m_Territories = new Grid<u8>(tilesW, tilesH);
429 
430 	// Reset territory counts for all players
431 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
432 	if (cmpPlayerManager && (size_t)cmpPlayerManager->GetNumPlayers() != m_TerritoryCellCounts.size())
433 		m_TerritoryCellCounts.resize(cmpPlayerManager->GetNumPlayers());
434 	for (u16& count : m_TerritoryCellCounts)
435 		count = 0;
436 
437 	// Find all territory influence entities
438 	CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
439 
440 	// Split influence entities into per-player lists, ignoring any with invalid properties
441 	std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
442 	for (const CComponentManager::InterfacePair& pair : influences)
443 	{
444 		entity_id_t ent = pair.first;
445 
446 		CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
447 		if (!cmpOwnership)
448 			continue;
449 
450 		// Ignore Gaia and unassigned or players we can't represent
451 		player_id_t owner = cmpOwnership->GetOwner();
452 		if (owner <= 0 || owner > TERRITORY_PLAYER_MASK)
453 			continue;
454 
455 		influenceEntities[owner].push_back(ent);
456 	}
457 
458 	// Store the overall best weight for comparison
459 	Grid<u32> bestWeightGrid(tilesW, tilesH);
460 	// store the root influences to mark territory as connected
461 	std::vector<entity_id_t> rootInfluenceEntities;
462 
463 	for (const std::pair<player_id_t, std::vector<entity_id_t> >& pair : influenceEntities)
464 	{
465 		// entityGrid stores the weight for a single entity, and is reset per entity
466 		Grid<u32> entityGrid(tilesW, tilesH);
467 		// playerGrid stores the combined weight of all entities for this player
468 		Grid<u32> playerGrid(tilesW, tilesH);
469 
470 		u8 owner = (u8)pair.first;
471 		const std::vector<entity_id_t>& ents = pair.second;
472 		// With 2^16 entities, we're safe against overflows as the weight is also limited to 2^16
473 		ENSURE(ents.size() < 1 << 16);
474 		// Compute the influence map of the current entity, then add it to the player grid
475 		for (entity_id_t ent : ents)
476 		{
477 			CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
478 			if (!cmpPosition || !cmpPosition->IsInWorld())
479 				continue;
480 
481 			CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
482 			u32 weight = cmpTerritoryInfluence->GetWeight();
483 			u32 radius = cmpTerritoryInfluence->GetRadius();
484 			if (weight == 0 || radius == 0)
485 				continue;
486 			u32 falloff = weight * (Pathfinding::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE).ToInt_RoundToNegInfinity() / radius;
487 
488 			CFixedVector2D pos = cmpPosition->GetPosition2D();
489 			u16 i, j;
490 			NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
491 
492 			if (cmpTerritoryInfluence->IsRoot())
493 				rootInfluenceEntities.push_back(ent);
494 
495 			// Initialise the tile under the entity
496 			entityGrid.set(i, j, weight);
497 			if (weight > bestWeightGrid.get(i, j))
498 			{
499 				bestWeightGrid.set(i, j, weight);
500 				m_Territories->set(i, j, owner);
501 			}
502 
503 			// Expand influences outwards
504 			FLOODFILL(i, j,
505 				u32 dg = falloff * m_CostGrid->get(nx, nz);
506 
507 				// diagonal neighbour -> multiply with approx sqrt(2)
508 				if (nx != x && nz != z)
509 					dg = (dg * 362) / 256;
510 
511 				// Don't expand if new cost is not better than previous value for that tile
512 				// (arranged to avoid underflow if entityGrid.get(x, z) < dg)
513 				if (entityGrid.get(x, z) <= entityGrid.get(nx, nz) + dg)
514 					continue;
515 
516 				// weight of this tile = weight of predecessor - falloff from predecessor
517 				u32 newWeight = entityGrid.get(x, z) - dg;
518 				u32 totalWeight = playerGrid.get(nx, nz) - entityGrid.get(nx, nz) + newWeight;
519 				playerGrid.set(nx, nz, totalWeight);
520 				entityGrid.set(nx, nz, newWeight);
521 				// if this weight is better than the best thus far, set the owner
522 				if (totalWeight > bestWeightGrid.get(nx, nz))
523 				{
524 					bestWeightGrid.set(nx, nz, totalWeight);
525 					m_Territories->set(nx, nz, owner);
526 				}
527 			);
528 
529 			entityGrid.reset();
530 		}
531 	}
532 
533 	// Detect territories connected to a 'root' influence (typically a civ center)
534 	// belonging to their player, and mark them with the connected flag
535 	for (entity_id_t ent : rootInfluenceEntities)
536 	{
537 		// (These components must be valid else the entities wouldn't be added to this list)
538 		CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
539 		CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
540 
541 		CFixedVector2D pos = cmpPosition->GetPosition2D();
542 		u16 i, j;
543 		NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
544 
545 		u8 owner = (u8)cmpOwnership->GetOwner();
546 
547 		if (m_Territories->get(i, j) != owner)
548 			continue;
549 
550 		m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK);
551 
552 		FLOODFILL(i, j,
553 			// Don't expand non-owner tiles, or tiles that already have a connected mask
554 			if (m_Territories->get(nx, nz) != owner)
555 				continue;
556 			m_Territories->set(nx, nz, owner | TERRITORY_CONNECTED_MASK);
557 			if (m_CostGrid->get(nx, nz) < m_ImpassableCost)
558 				++m_TerritoryCellCounts[owner];
559 		);
560 	}
561 
562 	// Then recomputes the blinking tiles
563        	CmpPtr<ICmpTerritoryDecayManager> cmpTerritoryDecayManager(GetSystemEntity());
564        	if (cmpTerritoryDecayManager)
565 	{
566 		size_t dirtyBlinkingID = m_DirtyBlinkingID;
567 	       	cmpTerritoryDecayManager->SetBlinkingEntities();
568 		m_DirtyBlinkingID = dirtyBlinkingID;
569 	}
570 }
571 
ComputeBoundaries()572 std::vector<STerritoryBoundary> CCmpTerritoryManager::ComputeBoundaries()
573 {
574 	PROFILE("ComputeBoundaries");
575 
576 	CalculateTerritories();
577 	ENSURE(m_Territories);
578 
579 	return CTerritoryBoundaryCalculator::ComputeBoundaries(m_Territories);
580 }
581 
GetTerritoryPercentage(player_id_t player)582 u8 CCmpTerritoryManager::GetTerritoryPercentage(player_id_t player)
583 {
584 	if (player <= 0 || (size_t)player > m_TerritoryCellCounts.size())
585 		return 0;
586 
587 	CalculateTerritories();
588 
589 	if (m_TerritoryTotalPassableCellCount == 0)
590 		return 0;
591 
592 	u8 percentage = (m_TerritoryCellCounts[player] * 100) / m_TerritoryTotalPassableCellCount;
593 	ENSURE(percentage <= 100);
594 	return percentage;
595 }
596 
UpdateBoundaryLines()597 void CCmpTerritoryManager::UpdateBoundaryLines()
598 {
599 	PROFILE("update boundary lines");
600 
601 	m_BoundaryLines.clear();
602 	m_DebugBoundaryLineNodes.clear();
603 
604 	if (!CRenderer::IsInitialised())
605 		return;
606 
607 	std::vector<STerritoryBoundary> boundaries = ComputeBoundaries();
608 
609 	CTextureProperties texturePropsBase("art/textures/misc/territory_border.png");
610 	texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
611 	texturePropsBase.SetMaxAnisotropy(2.f);
612 	CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
613 
614 	CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png");
615 	texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
616 	texturePropsMask.SetMaxAnisotropy(2.f);
617 	CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
618 
619 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
620 	if (!cmpPlayerManager)
621 		return;
622 
623 	for (size_t i = 0; i < boundaries.size(); ++i)
624 	{
625 		if (boundaries[i].points.empty())
626 			continue;
627 
628 		CColor color(1, 0, 1, 1);
629 		CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner));
630 		if (cmpPlayer)
631 			color = cmpPlayer->GetDisplayedColor();
632 
633 		m_BoundaryLines.push_back(SBoundaryLine());
634 		m_BoundaryLines.back().blinking = boundaries[i].blinking;
635 		m_BoundaryLines.back().owner = boundaries[i].owner;
636 		m_BoundaryLines.back().color = color;
637 		m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext();
638 		m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
639 		m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
640 		m_BoundaryLines.back().overlay.m_Color = color;
641 		m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
642 		m_BoundaryLines.back().overlay.m_Closed = true;
643 
644 		SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
645 		SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
646 
647 		std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
648 		for (size_t j = 0; j < boundaries[i].points.size(); ++j)
649 		{
650 			points.push_back(boundaries[i].points[j].X);
651 			points.push_back(boundaries[i].points[j].Y);
652 
653 			if (m_EnableLineDebugOverlays)
654 			{
655 				const size_t numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
656 				SOverlayLine overlayNode;
657 				if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
658 					overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
659 				else if (j < numHighlightNodes)
660 					overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
661 				else
662 					overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
663 
664 				overlayNode.m_Thickness = 1;
665 				SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
666 				m_DebugBoundaryLineNodes.push_back(overlayNode);
667 			}
668 		}
669 
670 	}
671 }
672 
Interpolate(float frameTime,float UNUSED (frameOffset))673 void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset))
674 {
675 	m_AnimTime += frameTime;
676 
677 	if (m_BoundaryLinesDirty)
678 	{
679 		UpdateBoundaryLines();
680 		m_BoundaryLinesDirty = false;
681 	}
682 
683 	for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
684 	{
685 		if (m_BoundaryLines[i].blinking)
686 		{
687 			CColor c = m_BoundaryLines[i].color;
688 			c.a *= 0.2f + 0.8f * fabsf((float)cos(m_AnimTime * M_PI)); // TODO: should let artists tweak this
689 			m_BoundaryLines[i].overlay.m_Color = c;
690 		}
691 	}
692 }
693 
RenderSubmit(SceneCollector & collector)694 void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
695 {
696 	if (!m_Visible)
697 		return;
698 
699 	for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
700 		collector.Submit(&m_BoundaryLines[i].overlay);
701 
702 	for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
703 		collector.Submit(&m_DebugBoundaryLineNodes[i]);
704 
705 }
706 
GetOwner(entity_pos_t x,entity_pos_t z)707 player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
708 {
709 	u16 i, j;
710 	if (!m_Territories)
711 	{
712 		CalculateTerritories();
713 		if (!m_Territories)
714 			return 0;
715 	}
716 
717 	NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
718 	return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
719 }
720 
GetNeighbours(entity_pos_t x,entity_pos_t z,bool filterConnected)721 std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
722 {
723 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
724 	if (!cmpPlayerManager)
725 		return std::vector<u32>();
726 
727 	std::vector<u32> ret(cmpPlayerManager->GetNumPlayers(), 0);
728 	CalculateTerritories();
729 	if (!m_Territories)
730 		return ret;
731 
732 	u16 i, j;
733 	NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
734 
735 	// calculate the neighbours
736 	player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
737 
738 	u16 tilesW = m_Territories->m_W;
739 	u16 tilesH = m_Territories->m_H;
740 
741 	// use a flood-fill algorithm that fills up to the borders and remembers the owners
742 	Grid<bool> markerGrid(tilesW, tilesH);
743 	markerGrid.set(i, j, true);
744 
745 	FLOODFILL(i, j,
746 		if (markerGrid.get(nx, nz))
747 			continue;
748 		// mark the tile as visited in any case
749 		markerGrid.set(nx, nz, true);
750 		int owner = m_Territories->get(nx, nz) & TERRITORY_PLAYER_MASK;
751 		if (owner != thisOwner)
752 		{
753 			if (owner == 0 || !filterConnected || (m_Territories->get(nx, nz) & TERRITORY_CONNECTED_MASK) != 0)
754 				ret[owner]++; // add player to the neighbour list when requested
755 			continue; // don't expand non-owner tiles further
756 		}
757 	);
758 
759 	return ret;
760 }
761 
IsConnected(entity_pos_t x,entity_pos_t z)762 bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
763 {
764 	u16 i, j;
765 	CalculateTerritories();
766 	if (!m_Territories)
767 		return false;
768 
769 	NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
770 	return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0;
771 }
772 
SetTerritoryBlinking(entity_pos_t x,entity_pos_t z,bool enable)773 void CCmpTerritoryManager::SetTerritoryBlinking(entity_pos_t x, entity_pos_t z, bool enable)
774 {
775 	CalculateTerritories();
776 	if (!m_Territories)
777 		return;
778 
779 	u16 i, j;
780 	NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
781 
782 	u16 tilesW = m_Territories->m_W;
783 	u16 tilesH = m_Territories->m_H;
784 
785 	player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
786 
787 	FLOODFILL(i, j,
788 		u8 bitmask = m_Territories->get(nx, nz);
789 		if ((bitmask & TERRITORY_PLAYER_MASK) != thisOwner)
790 			continue;
791 		u8 blinking = bitmask & TERRITORY_BLINKING_MASK;
792 		if (enable && !blinking)
793 			m_Territories->set(nx, nz, bitmask | TERRITORY_BLINKING_MASK);
794 		else if (!enable && blinking)
795 			m_Territories->set(nx, nz, bitmask & ~TERRITORY_BLINKING_MASK);
796 		else
797 			continue;
798 	);
799 	++m_DirtyBlinkingID;
800 	m_BoundaryLinesDirty = true;
801 }
802 
IsTerritoryBlinking(entity_pos_t x,entity_pos_t z)803 bool CCmpTerritoryManager::IsTerritoryBlinking(entity_pos_t x, entity_pos_t z)
804 {
805 	CalculateTerritories();
806 	if (!m_Territories)
807 		return false;
808 
809 	u16 i, j;
810 	NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
811 	return (m_Territories->get(i, j) & TERRITORY_BLINKING_MASK) != 0;
812 }
813 
UpdateColors()814 void CCmpTerritoryManager::UpdateColors()
815 {
816 	m_ColorChanged = true;
817 
818 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
819 	if (!cmpPlayerManager)
820 		return;
821 
822 	for (SBoundaryLine& boundaryLine : m_BoundaryLines)
823 	{
824 		CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaryLine.owner));
825 		if (!cmpPlayer)
826 			continue;
827 
828 		boundaryLine.color = cmpPlayer->GetDisplayedColor();
829 		boundaryLine.overlay.m_Color = boundaryLine.color;
830 	}
831 }
832 
TerritoryOverlay(CCmpTerritoryManager & manager)833 TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) :
834 	TerrainTextureOverlay((float)Pathfinding::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE),
835 	m_TerritoryManager(manager)
836 { }
837 
BuildTextureRGBA(u8 * data,size_t w,size_t h)838 void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h)
839 {
840 	for (size_t j = 0; j < h; ++j)
841 	{
842 		for (size_t i = 0; i < w; ++i)
843 		{
844 			SColor4ub color;
845 			u8 id = (m_TerritoryManager.m_Territories->get((int)i, (int)j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
846 			color = GetColor(id, 64);
847 			*data++ = color.R;
848 			*data++ = color.G;
849 			*data++ = color.B;
850 			*data++ = color.A;
851 		}
852 	}
853 }
854 
855 #undef FLOODFILL
856