LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpTerritoryManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 45 377 11.9 %
Date: 2023-01-19 00:18:29 Functions: 12 44 27.3 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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           0 : class TerritoryOverlay final : public TerrainTextureOverlay
      49             : {
      50             :     NONCOPYABLE(TerritoryOverlay);
      51             : public:
      52             :     CCmpTerritoryManager& m_TerritoryManager;
      53             : 
      54             :     TerritoryOverlay(CCmpTerritoryManager& manager);
      55             :     void BuildTextureRGBA(u8* data, size_t w, size_t h) override;
      56             : };
      57             : 
      58           9 : class CCmpTerritoryManager : public ICmpTerritoryManager
      59             : {
      60             : public:
      61         116 :     static void ClassInit(CComponentManager& componentManager)
      62             :     {
      63         116 :         componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
      64         116 :         componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
      65         116 :         componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
      66         116 :         componentManager.SubscribeGloballyToMessageType(MT_ValueModification);
      67         116 :         componentManager.SubscribeToMessageType(MT_ObstructionMapShapeChanged);
      68         116 :         componentManager.SubscribeToMessageType(MT_TerrainChanged);
      69         116 :         componentManager.SubscribeToMessageType(MT_WaterChanged);
      70         116 :         componentManager.SubscribeToMessageType(MT_Update);
      71         116 :         componentManager.SubscribeToMessageType(MT_Interpolate);
      72         116 :         componentManager.SubscribeToMessageType(MT_RenderSubmit);
      73         116 :     }
      74             : 
      75           6 :     DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
      76             : 
      77         116 :     static std::string GetSchema()
      78             :     {
      79         116 :         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           0 :     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             : 
     120           3 :     void Init(const CParamNode& UNUSED(paramNode)) override
     121             :     {
     122           3 :         m_Territories = NULL;
     123           3 :         m_CostGrid = NULL;
     124           3 :         m_DebugOverlay = NULL;
     125             : //      m_DebugOverlay = new TerritoryOverlay(*this);
     126           3 :         m_BoundaryLinesDirty = true;
     127           3 :         m_TriggerEvent = true;
     128           3 :         m_EnableLineDebugOverlays = false;
     129           3 :         m_DirtyID = 1;
     130           3 :         m_DirtyBlinkingID = 1;
     131           3 :         m_Visible = true;
     132           3 :         m_ColorChanged = false;
     133             : 
     134           3 :         m_AnimTime = 0.0;
     135             : 
     136           3 :         m_TerritoryTotalPassableCellCount = 0;
     137             : 
     138             :         // Register Relax NG validator
     139           3 :         CXeromyces::AddValidator(g_VFS, "territorymanager", "simulation/data/territorymanager.rng");
     140             : 
     141           6 :         CParamNode externalParamNode;
     142           3 :         CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml", "territorymanager");
     143             : 
     144           3 :         int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt();
     145           3 :         ENSURE(0 <= impassableCost && impassableCost <= 255);
     146           3 :         m_ImpassableCost = (u8)impassableCost;
     147           3 :         m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat();
     148           3 :         m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat();
     149           3 :     }
     150             : 
     151           3 :     void Deinit() override
     152             :     {
     153           3 :         SAFE_DELETE(m_Territories);
     154           3 :         SAFE_DELETE(m_CostGrid);
     155           3 :         SAFE_DELETE(m_DebugOverlay);
     156           3 :     }
     157             : 
     158           0 :     void Serialize(ISerializer& serialize) override
     159             :     {
     160             :         // Territory state can be recomputed as required, so we don't need to serialize any of it.
     161           0 :         serialize.Bool("trigger event", m_TriggerEvent);
     162           0 :     }
     163             : 
     164           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
     165             :     {
     166           0 :         Init(paramNode);
     167           0 :         deserialize.Bool("trigger event", m_TriggerEvent);
     168           0 :     }
     169             : 
     170           0 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
     171             :     {
     172           0 :         switch (msg.GetType())
     173             :         {
     174           0 :         case MT_OwnershipChanged:
     175             :         {
     176           0 :             const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
     177           0 :             MakeDirtyIfRelevantEntity(msgData.entity);
     178           0 :             break;
     179             :         }
     180           0 :         case MT_PlayerColorChanged:
     181             :         {
     182           0 :             MakeDirty();
     183           0 :             break;
     184             :         }
     185           0 :         case MT_PositionChanged:
     186             :         {
     187           0 :             const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
     188           0 :             MakeDirtyIfRelevantEntity(msgData.entity);
     189           0 :             break;
     190             :         }
     191           0 :         case MT_ValueModification:
     192             :         {
     193           0 :             const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
     194           0 :             if (msgData.component == L"TerritoryInfluence")
     195           0 :                 MakeDirty();
     196           0 :             break;
     197             :         }
     198           0 :         case MT_ObstructionMapShapeChanged:
     199             :         case MT_TerrainChanged:
     200             :         case MT_WaterChanged:
     201             :         {
     202             :             // also recalculate the cost grid to support atlas changes
     203           0 :             SAFE_DELETE(m_CostGrid);
     204           0 :             MakeDirty();
     205           0 :             break;
     206             :         }
     207           0 :         case MT_Update:
     208             :         {
     209           0 :             if (m_TriggerEvent)
     210             :             {
     211           0 :                 m_TriggerEvent = false;
     212           0 :                 GetSimContext().GetComponentManager().BroadcastMessage(CMessageTerritoriesChanged());
     213             :             }
     214           0 :             break;
     215             :         }
     216           0 :         case MT_Interpolate:
     217             :         {
     218           0 :             const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
     219           0 :             Interpolate(msgData.deltaSimTime, msgData.offset);
     220           0 :             break;
     221             :         }
     222           0 :         case MT_RenderSubmit:
     223             :         {
     224           0 :             const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     225           0 :             RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
     226           0 :             break;
     227             :         }
     228             :         }
     229           0 :     }
     230             : 
     231             :     // Check whether the entity is either a settlement or territory influence;
     232             :     // ignore any others
     233           0 :     void MakeDirtyIfRelevantEntity(entity_id_t ent)
     234             :     {
     235           0 :         CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
     236           0 :         if (cmpTerritoryInfluence)
     237           0 :             MakeDirty();
     238           0 :     }
     239             : 
     240           0 :     const Grid<u8>& GetTerritoryGrid() override
     241             :     {
     242           0 :         CalculateTerritories();
     243           0 :         ENSURE(m_Territories);
     244           0 :         return *m_Territories;
     245             :     }
     246             : 
     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;
     250             : 
     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;
     253             : 
     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.
     258             : 
     259             :     size_t m_DirtyID;
     260             :     size_t m_DirtyBlinkingID;
     261             : 
     262             :     bool m_ColorChanged;
     263             : 
     264           0 :     void MakeDirty()
     265             :     {
     266           0 :         SAFE_DELETE(m_Territories);
     267           0 :         ++m_DirtyID;
     268           0 :         m_BoundaryLinesDirty = true;
     269           0 :         m_TriggerEvent = true;
     270           0 :     }
     271             : 
     272           0 :     bool NeedUpdateTexture(size_t* dirtyID) override
     273             :     {
     274           0 :         if (*dirtyID == m_DirtyID && !m_ColorChanged)
     275           0 :             return false;
     276             : 
     277           0 :         *dirtyID = m_DirtyID;
     278           0 :         m_ColorChanged = false;
     279           0 :         return true;
     280             :     }
     281             : 
     282           0 :     bool NeedUpdateAI(size_t* dirtyID, size_t* dirtyBlinkingID) const override
     283             :     {
     284           0 :         if (*dirtyID == m_DirtyID && *dirtyBlinkingID == m_DirtyBlinkingID)
     285           0 :             return false;
     286             : 
     287           0 :         *dirtyID = m_DirtyID;
     288           0 :         *dirtyBlinkingID = m_DirtyBlinkingID;
     289           0 :         return true;
     290             :     }
     291             : 
     292             :     void CalculateCostGrid();
     293             : 
     294             :     void CalculateTerritories();
     295             : 
     296             :     u8 GetTerritoryPercentage(player_id_t player) override;
     297             : 
     298             :     std::vector<STerritoryBoundary> ComputeBoundaries();
     299             : 
     300             :     void UpdateBoundaryLines();
     301             : 
     302             :     void Interpolate(float frameTime, float frameOffset);
     303             : 
     304             :     void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
     305             : 
     306           0 :     void SetVisibility(bool visible) override
     307             :     {
     308           0 :         m_Visible = visible;
     309           0 :     }
     310             : 
     311             :     void UpdateColors() override;
     312             : 
     313             : private:
     314             : 
     315             :     bool m_Visible;
     316             : };
     317             : 
     318         116 : REGISTER_COMPONENT_TYPE(TerritoryManager)
     319             : 
     320             : // Tile data type, for easier accessing of coordinates
     321             : struct Tile
     322             : {
     323           0 :     Tile(u16 i, u16 j) : x(i), z(j) { }
     324             :     u16 x, z;
     325             : };
     326             : 
     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())\
     340             :         {\
     341             :             u16 posX = openTiles.front().x;\
     342             :             u16 posZ = openTiles.front().z;\
     343             :             openTiles.pop();\
     344             :             for (int n = 0; n < NUM_NEIGHBOURS; ++n)\
     345             :             {\
     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);\
     353             :             }\
     354             :         }\
     355             :     }\
     356             :     while (false)
     357             : 
     358             : /**
     359             :  * Compute the tile indexes on the grid nearest to a given point
     360             :  */
     361           0 : static void NearestTerritoryTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
     362             : {
     363           0 :     entity_pos_t scale = Pathfinding::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE;
     364           0 :     i = Clamp((x / scale).ToInt_RoundToNegInfinity(), 0, w - 1);
     365           0 :     j = Clamp((z / scale).ToInt_RoundToNegInfinity(), 0, h - 1);
     366           0 : }
     367             : 
     368           0 : void CCmpTerritoryManager::CalculateCostGrid()
     369             : {
     370           0 :     if (m_CostGrid)
     371           0 :         return;
     372             : 
     373           0 :     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
     374           0 :     if (!cmpPathfinder)
     375           0 :         return;
     376             : 
     377           0 :     pass_class_t passClassTerritory = cmpPathfinder->GetPassabilityClass("default-terrain-only");
     378           0 :     pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
     379             : 
     380           0 :     const Grid<NavcellData>& passGrid = cmpPathfinder->GetPassabilityGrid();
     381             : 
     382           0 :     int tilesW = passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE;
     383           0 :     int tilesH = passGrid.m_H / NAVCELLS_PER_TERRITORY_TILE;
     384             : 
     385           0 :     m_CostGrid = new Grid<u8>(tilesW, tilesH);
     386           0 :     m_TerritoryTotalPassableCellCount = 0;
     387             : 
     388           0 :     for (int i = 0; i < tilesW; ++i)
     389             :     {
     390           0 :         for (int j = 0; j < tilesH; ++j)
     391             :         {
     392           0 :             NavcellData c = 0;
     393           0 :             for (u16 di = 0; di < NAVCELLS_PER_TERRITORY_TILE; ++di)
     394           0 :                 for (u16 dj = 0; dj < NAVCELLS_PER_TERRITORY_TILE; ++dj)
     395           0 :                     c |= passGrid.get(
     396           0 :                         i * NAVCELLS_PER_TERRITORY_TILE + di,
     397           0 :                         j * NAVCELLS_PER_TERRITORY_TILE + dj);
     398           0 :             if (!IS_PASSABLE(c, passClassTerritory))
     399           0 :                 m_CostGrid->set(i, j, m_ImpassableCost);
     400           0 :             else if (!IS_PASSABLE(c, passClassUnrestricted))
     401           0 :                 m_CostGrid->set(i, j, 255); // off the world; use maximum cost
     402             :             else
     403             :             {
     404           0 :                 m_CostGrid->set(i, j, 1);
     405           0 :                 ++m_TerritoryTotalPassableCellCount;
     406             :             }
     407             :         }
     408             :     }
     409             : }
     410             : 
     411           0 : void CCmpTerritoryManager::CalculateTerritories()
     412             : {
     413           0 :     if (m_Territories)
     414           0 :         return;
     415             : 
     416           0 :     PROFILE("CalculateTerritories");
     417             : 
     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           0 :     CalculateCostGrid();
     421           0 :     if (!m_CostGrid)
     422           0 :         return;
     423             : 
     424           0 :     const u16 tilesW = m_CostGrid->m_W;
     425           0 :     const u16 tilesH = m_CostGrid->m_H;
     426             : 
     427           0 :     m_Territories = new Grid<u8>(tilesW, tilesH);
     428             : 
     429             :     // Reset territory counts for all players
     430           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     431           0 :     if (cmpPlayerManager && (size_t)cmpPlayerManager->GetNumPlayers() != m_TerritoryCellCounts.size())
     432           0 :         m_TerritoryCellCounts.resize(cmpPlayerManager->GetNumPlayers());
     433           0 :     for (u16& count : m_TerritoryCellCounts)
     434           0 :         count = 0;
     435             : 
     436             :     // Find all territory influence entities
     437           0 :     CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
     438             : 
     439             :     // Split influence entities into per-player lists, ignoring any with invalid properties
     440           0 :     std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
     441           0 :     for (const CComponentManager::InterfacePair& pair : influences)
     442             :     {
     443           0 :         entity_id_t ent = pair.first;
     444             : 
     445           0 :         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
     446           0 :         if (!cmpOwnership)
     447           0 :             continue;
     448             : 
     449             :         // Ignore Gaia and unassigned or players we can't represent
     450           0 :         player_id_t owner = cmpOwnership->GetOwner();
     451           0 :         if (owner <= 0 || owner > TERRITORY_PLAYER_MASK)
     452           0 :             continue;
     453             : 
     454           0 :         influenceEntities[owner].push_back(ent);
     455             :     }
     456             : 
     457             :     // Store the overall best weight for comparison
     458           0 :     Grid<u32> bestWeightGrid(tilesW, tilesH);
     459             :     // store the root influences to mark territory as connected
     460           0 :     std::vector<entity_id_t> rootInfluenceEntities;
     461             : 
     462           0 :     for (const std::pair<const player_id_t, std::vector<entity_id_t>>& pair : influenceEntities)
     463             :     {
     464             :         // entityGrid stores the weight for a single entity, and is reset per entity
     465           0 :         Grid<u32> entityGrid(tilesW, tilesH);
     466             :         // playerGrid stores the combined weight of all entities for this player
     467           0 :         Grid<u32> playerGrid(tilesW, tilesH);
     468             : 
     469           0 :         u8 owner = static_cast<u8>(pair.first);
     470           0 :         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           0 :         ENSURE(ents.size() < 1 << 16);
     473             :         // Compute the influence map of the current entity, then add it to the player grid
     474           0 :         for (entity_id_t ent : ents)
     475             :         {
     476           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
     477           0 :             if (!cmpPosition || !cmpPosition->IsInWorld())
     478           0 :                 continue;
     479             : 
     480           0 :             CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent);
     481           0 :             u32 weight = cmpTerritoryInfluence->GetWeight();
     482           0 :             u32 radius = cmpTerritoryInfluence->GetRadius();
     483           0 :             if (weight == 0 || radius == 0)
     484           0 :                 continue;
     485           0 :             u32 falloff = weight * (Pathfinding::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE).ToInt_RoundToNegInfinity() / radius;
     486             : 
     487           0 :             CFixedVector2D pos = cmpPosition->GetPosition2D();
     488             :             u16 i, j;
     489           0 :             NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
     490             : 
     491           0 :             if (cmpTerritoryInfluence->IsRoot())
     492           0 :                 rootInfluenceEntities.push_back(ent);
     493             : 
     494             :             // Initialise the tile under the entity
     495           0 :             entityGrid.set(i, j, weight);
     496           0 :             if (weight > bestWeightGrid.get(i, j))
     497             :             {
     498           0 :                 bestWeightGrid.set(i, j, weight);
     499           0 :                 m_Territories->set(i, j, owner);
     500             :             }
     501             : 
     502             :             // Expand influences outwards
     503           0 :             FLOODFILL(i, j,
     504             :                 u32 dg = falloff * m_CostGrid->get(nx, nz);
     505             : 
     506             :                 // diagonal neighbour -> multiply with approx sqrt(2)
     507             :                 if (nx != posX && nz != posZ)
     508             :                     dg = (dg * 362) / 256;
     509             : 
     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;
     514             : 
     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))
     522             :                 {
     523             :                     bestWeightGrid.set(nx, nz, totalWeight);
     524             :                     m_Territories->set(nx, nz, owner);
     525             :                 }
     526             :             );
     527             : 
     528           0 :             entityGrid.reset();
     529             :         }
     530             :     }
     531             : 
     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           0 :     for (entity_id_t ent : rootInfluenceEntities)
     535             :     {
     536             :         // (These components must be valid else the entities wouldn't be added to this list)
     537           0 :         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent);
     538           0 :         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
     539             : 
     540           0 :         CFixedVector2D pos = cmpPosition->GetPosition2D();
     541             :         u16 i, j;
     542           0 :         NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
     543             : 
     544           0 :         u8 owner = (u8)cmpOwnership->GetOwner();
     545             : 
     546           0 :         if (m_Territories->get(i, j) != owner)
     547           0 :             continue;
     548             : 
     549           0 :         m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK);
     550             : 
     551           0 :         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];
     558             :         );
     559             :     }
     560             : 
     561             :     // Then recomputes the blinking tiles
     562           0 :         CmpPtr<ICmpTerritoryDecayManager> cmpTerritoryDecayManager(GetSystemEntity());
     563           0 :         if (cmpTerritoryDecayManager)
     564             :     {
     565           0 :         size_t dirtyBlinkingID = m_DirtyBlinkingID;
     566           0 :             cmpTerritoryDecayManager->SetBlinkingEntities();
     567           0 :         m_DirtyBlinkingID = dirtyBlinkingID;
     568             :     }
     569             : }
     570             : 
     571           0 : std::vector<STerritoryBoundary> CCmpTerritoryManager::ComputeBoundaries()
     572             : {
     573           0 :     PROFILE("ComputeBoundaries");
     574             : 
     575           0 :     CalculateTerritories();
     576           0 :     ENSURE(m_Territories);
     577             : 
     578           0 :     return CTerritoryBoundaryCalculator::ComputeBoundaries(m_Territories);
     579             : }
     580             : 
     581           0 : u8 CCmpTerritoryManager::GetTerritoryPercentage(player_id_t player)
     582             : {
     583           0 :     if (player <= 0 || static_cast<size_t>(player) >= m_TerritoryCellCounts.size())
     584           0 :         return 0;
     585             : 
     586           0 :     CalculateTerritories();
     587             : 
     588             :     // Territories may have been recalculated, check whether player is still there.
     589           0 :     if (m_TerritoryTotalPassableCellCount == 0 || static_cast<size_t>(player) >= m_TerritoryCellCounts.size())
     590           0 :         return 0;
     591             : 
     592           0 :     u8 percentage = (m_TerritoryCellCounts[player] * 100) / m_TerritoryTotalPassableCellCount;
     593           0 :     ENSURE(percentage <= 100);
     594           0 :     return percentage;
     595             : }
     596             : 
     597           0 : void CCmpTerritoryManager::UpdateBoundaryLines()
     598             : {
     599           0 :     PROFILE("update boundary lines");
     600             : 
     601           0 :     m_BoundaryLines.clear();
     602           0 :     m_DebugBoundaryLineNodes.clear();
     603             : 
     604           0 :     if (!CRenderer::IsInitialised())
     605           0 :         return;
     606             : 
     607           0 :     std::vector<STerritoryBoundary> boundaries = ComputeBoundaries();
     608             : 
     609           0 :     CTextureProperties texturePropsBase("art/textures/misc/territory_border.png");
     610           0 :     texturePropsBase.SetAddressMode(
     611             :         Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     612             :         Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     613           0 :     texturePropsBase.SetAnisotropicFilter(true);
     614           0 :     CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     615             : 
     616           0 :     CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png");
     617           0 :     texturePropsMask.SetAddressMode(
     618             :         Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     619             :         Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     620           0 :     texturePropsMask.SetAnisotropicFilter(true);
     621           0 :     CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     622             : 
     623           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     624           0 :     if (!cmpPlayerManager)
     625           0 :         return;
     626             : 
     627           0 :     for (size_t i = 0; i < boundaries.size(); ++i)
     628             :     {
     629           0 :         if (boundaries[i].points.empty())
     630           0 :             continue;
     631             : 
     632           0 :         CColor color(1, 0, 1, 1);
     633           0 :         CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner));
     634           0 :         if (cmpPlayer)
     635           0 :             color = cmpPlayer->GetDisplayedColor();
     636             : 
     637           0 :         m_BoundaryLines.push_back(SBoundaryLine());
     638           0 :         m_BoundaryLines.back().blinking = boundaries[i].blinking;
     639           0 :         m_BoundaryLines.back().owner = boundaries[i].owner;
     640           0 :         m_BoundaryLines.back().color = color;
     641           0 :         m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext();
     642           0 :         m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
     643           0 :         m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
     644           0 :         m_BoundaryLines.back().overlay.m_Color = color;
     645           0 :         m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
     646           0 :         m_BoundaryLines.back().overlay.m_Closed = true;
     647             : 
     648           0 :         SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
     649           0 :         SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
     650             : 
     651           0 :         std::vector<CVector2D>& points = m_BoundaryLines.back().overlay.m_Coords;
     652           0 :         for (size_t j = 0; j < boundaries[i].points.size(); ++j)
     653             :         {
     654           0 :             points.push_back(boundaries[i].points[j]);
     655             : 
     656           0 :             if (m_EnableLineDebugOverlays)
     657             :             {
     658           0 :                 const size_t numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
     659           0 :                 SOverlayLine overlayNode;
     660           0 :                 if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
     661           0 :                     overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     662           0 :                 else if (j < numHighlightNodes)
     663           0 :                     overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     664             :                 else
     665           0 :                     overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     666             : 
     667           0 :                 overlayNode.m_Thickness = 0.1f;
     668           0 :                 SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
     669           0 :                 m_DebugBoundaryLineNodes.push_back(overlayNode);
     670             :             }
     671             :         }
     672             : 
     673             :     }
     674             : }
     675             : 
     676           0 : void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset))
     677             : {
     678           0 :     m_AnimTime += frameTime;
     679             : 
     680           0 :     if (m_BoundaryLinesDirty)
     681             :     {
     682           0 :         UpdateBoundaryLines();
     683           0 :         m_BoundaryLinesDirty = false;
     684             :     }
     685             : 
     686           0 :     for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
     687             :     {
     688           0 :         if (m_BoundaryLines[i].blinking)
     689             :         {
     690           0 :             CColor c = m_BoundaryLines[i].color;
     691           0 :             c.a *= 0.2f + 0.8f * fabsf((float)cos(m_AnimTime * M_PI)); // TODO: should let artists tweak this
     692           0 :             m_BoundaryLines[i].overlay.m_Color = c;
     693             :         }
     694             :     }
     695           0 : }
     696             : 
     697           0 : void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
     698             : {
     699           0 :     if (!m_Visible)
     700           0 :         return;
     701             : 
     702           0 :     for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
     703             :     {
     704           0 :         if (culling && !m_BoundaryLines[i].overlay.IsVisibleInFrustum(frustum))
     705           0 :             continue;
     706           0 :         collector.Submit(&m_BoundaryLines[i].overlay);
     707             :     }
     708             : 
     709           0 :     for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
     710           0 :         collector.Submit(&m_DebugBoundaryLineNodes[i]);
     711             : 
     712             : }
     713             : 
     714           0 : player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
     715             : {
     716             :     u16 i, j;
     717           0 :     if (!m_Territories)
     718             :     {
     719           0 :         CalculateTerritories();
     720           0 :         if (!m_Territories)
     721           0 :             return 0;
     722             :     }
     723             : 
     724           0 :     NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     725           0 :     return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
     726             : }
     727             : 
     728           0 : std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
     729             : {
     730           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     731           0 :     if (!cmpPlayerManager)
     732           0 :         return std::vector<u32>();
     733             : 
     734           0 :     std::vector<u32> ret(cmpPlayerManager->GetNumPlayers(), 0);
     735           0 :     CalculateTerritories();
     736           0 :     if (!m_Territories)
     737           0 :         return ret;
     738             : 
     739             :     u16 i, j;
     740           0 :     NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     741             : 
     742             :     // calculate the neighbours
     743           0 :     player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
     744             : 
     745           0 :     u16 tilesW = m_Territories->m_W;
     746           0 :     u16 tilesH = m_Territories->m_H;
     747             : 
     748             :     // use a flood-fill algorithm that fills up to the borders and remembers the owners
     749           0 :     Grid<bool> markerGrid(tilesW, tilesH);
     750           0 :     markerGrid.set(i, j, true);
     751             : 
     752           0 :     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)
     759             :         {
     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
     763             :         }
     764             :     );
     765             : 
     766           0 :     return ret;
     767             : }
     768             : 
     769           0 : bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
     770             : {
     771             :     u16 i, j;
     772           0 :     CalculateTerritories();
     773           0 :     if (!m_Territories)
     774           0 :         return false;
     775             : 
     776           0 :     NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     777           0 :     return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0;
     778             : }
     779             : 
     780           0 : void CCmpTerritoryManager::SetTerritoryBlinking(entity_pos_t x, entity_pos_t z, bool enable)
     781             : {
     782           0 :     CalculateTerritories();
     783           0 :     if (!m_Territories)
     784           0 :         return;
     785             : 
     786             :     u16 i, j;
     787           0 :     NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     788             : 
     789           0 :     u16 tilesW = m_Territories->m_W;
     790           0 :     u16 tilesH = m_Territories->m_H;
     791             : 
     792           0 :     player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
     793             : 
     794           0 :     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;
     805             :     );
     806           0 :     ++m_DirtyBlinkingID;
     807           0 :     m_BoundaryLinesDirty = true;
     808             : }
     809             : 
     810           0 : bool CCmpTerritoryManager::IsTerritoryBlinking(entity_pos_t x, entity_pos_t z)
     811             : {
     812           0 :     CalculateTerritories();
     813           0 :     if (!m_Territories)
     814           0 :         return false;
     815             : 
     816             :     u16 i, j;
     817           0 :     NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     818           0 :     return (m_Territories->get(i, j) & TERRITORY_BLINKING_MASK) != 0;
     819             : }
     820             : 
     821           0 : void CCmpTerritoryManager::UpdateColors()
     822             : {
     823           0 :     m_ColorChanged = true;
     824             : 
     825           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     826           0 :     if (!cmpPlayerManager)
     827           0 :         return;
     828             : 
     829           0 :     for (SBoundaryLine& boundaryLine : m_BoundaryLines)
     830             :     {
     831           0 :         CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaryLine.owner));
     832           0 :         if (!cmpPlayer)
     833           0 :             continue;
     834             : 
     835           0 :         boundaryLine.color = cmpPlayer->GetDisplayedColor();
     836           0 :         boundaryLine.overlay.m_Color = boundaryLine.color;
     837             :     }
     838             : }
     839             : 
     840           0 : TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) :
     841             :     TerrainTextureOverlay((float)Pathfinding::NAVCELLS_PER_TERRAIN_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE),
     842           0 :     m_TerritoryManager(manager)
     843           0 : { }
     844             : 
     845           0 : void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h)
     846             : {
     847           0 :     for (size_t j = 0; j < h; ++j)
     848             :     {
     849           0 :         for (size_t i = 0; i < w; ++i)
     850             :         {
     851           0 :             SColor4ub color;
     852           0 :             u8 id = (m_TerritoryManager.m_Territories->get((int)i, (int)j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
     853           0 :             color = GetColor(id, 64);
     854           0 :             *data++ = color.R;
     855           0 :             *data++ = color.G;
     856           0 :             *data++ = color.B;
     857           0 :             *data++ = color.A;
     858             :         }
     859             :     }
     860           3 : }
     861             : 
     862             : #undef FLOODFILL

Generated by: LCOV version 1.13