/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpTerritoryManager.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/Overlay.h" #include "renderer/Renderer.h" #include "renderer/Scene.h" #include "renderer/TerrainOverlay.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpSettlement.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpTerritoryInfluence.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Grid.h" #include "simulation2/helpers/PriorityQueue.h" #include "simulation2/helpers/Render.h" class CCmpTerritoryManager; class TerritoryOverlay : public TerrainOverlay { NONCOPYABLE(TerritoryOverlay); public: CCmpTerritoryManager& m_TerritoryManager; TerritoryOverlay(CCmpTerritoryManager& manager) : m_TerritoryManager(manager) { } virtual void StartRender(); virtual void ProcessTile(ssize_t i, ssize_t j); }; class CCmpTerritoryManager : public ICmpTerritoryManager { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged); componentManager.SubscribeGloballyToMessageType(MT_PositionChanged); componentManager.SubscribeToMessageType(MT_TerrainChanged); componentManager.SubscribeToMessageType(MT_Update); componentManager.SubscribeToMessageType(MT_Interpolate); componentManager.SubscribeToMessageType(MT_RenderSubmit); } DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager) static std::string GetSchema() { return ""; } u8 m_ImpassableCost; float m_BorderThickness; float m_BorderSeparation; // Player ID in lower 7 bits; connected flag in high bit Grid* m_Territories; // Set to true when territories change; will send a TerritoriesChanged message // during the Update phase bool m_TriggerEvent; struct SBoundaryLine { bool connected; CColor color; SOverlayTexturedLine overlay; }; std::vector m_BoundaryLines; bool m_BoundaryLinesDirty; double m_AnimTime; // time since start of rendering, in seconds TerritoryOverlay* m_DebugOverlay; virtual void Init(const CParamNode& UNUSED(paramNode)) { m_Territories = NULL; m_DebugOverlay = NULL; // m_DebugOverlay = new TerritoryOverlay(*this); m_BoundaryLinesDirty = true; m_TriggerEvent = true; m_DirtyID = 1; m_AnimTime = 0.0; CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml"); int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt(); ENSURE(0 <= impassableCost && impassableCost <= 255); m_ImpassableCost = (u8)impassableCost; m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat(); m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat(); } virtual void Deinit() { SAFE_DELETE(m_Territories); SAFE_DELETE(m_DebugOverlay); } virtual void Serialize(ISerializer& serialize) { // TODO } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_OwnershipChanged: { const CMessageOwnershipChanged& msgData = static_cast (msg); MakeDirtyIfRelevantEntity(msgData.entity); break; } case MT_PositionChanged: { const CMessagePositionChanged& msgData = static_cast (msg); MakeDirtyIfRelevantEntity(msgData.entity); break; } case MT_TerrainChanged: { MakeDirty(); break; } case MT_Update: { if (m_TriggerEvent) { m_TriggerEvent = false; CMessageTerritoriesChanged msg; GetSimContext().GetComponentManager().BroadcastMessage(msg); } break; } case MT_Interpolate: { const CMessageInterpolate& msgData = static_cast (msg); Interpolate(msgData.frameTime, msgData.offset); break; } case MT_RenderSubmit: { const CMessageRenderSubmit& msgData = static_cast (msg); RenderSubmit(msgData.collector); break; } } } // Check whether the entity is either a settlement or territory influence; // ignore any others void MakeDirtyIfRelevantEntity(entity_id_t ent) { CmpPtr cmpSettlement(GetSimContext(), ent); if (!cmpSettlement.null()) MakeDirty(); CmpPtr cmpTerritoryInfluence(GetSimContext(), ent); if (!cmpTerritoryInfluence.null()) MakeDirty(); } virtual const Grid& GetTerritoryGrid() { CalculateTerritories(); ENSURE(m_Territories); return *m_Territories; } virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z); virtual bool IsConnected(entity_pos_t x, entity_pos_t z); // To support lazy updates of territory render data, // we maintain a DirtyID here and increment it whenever territories change; // if a caller has a lower DirtyID then it needs to be updated. size_t m_DirtyID; void MakeDirty() { SAFE_DELETE(m_Territories); ++m_DirtyID; m_BoundaryLinesDirty = true; m_TriggerEvent = true; } virtual bool NeedUpdate(size_t* dirtyID) { if (*dirtyID != m_DirtyID) { *dirtyID = m_DirtyID; return true; } return false; } void CalculateTerritories(); /** * Updates @p grid based on the obstruction shapes of all entities with * a TerritoryInfluence component. Grid cells are 0 if no influence, * or 1+c if the influence have cost c (assumed between 0 and 254). */ void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid); struct TerritoryBoundary { bool connected; player_id_t owner; std::vector points; }; std::vector ComputeBoundaries(); void UpdateBoundaryLines(); void Interpolate(float frameTime, float frameOffset); void RenderSubmit(SceneCollector& collector); }; REGISTER_COMPONENT_TYPE(TerritoryManager) /* We compute the territory influence of an entity with a kind of best-first search, storing an 'open' list of tiles that have not yet been processed, then taking the highest-weight tile (closest to origin) and updating the weight of extending to each neighbour (based on radius-determining 'falloff' value, adjusted by terrain movement cost), and repeating until all tiles are processed. */ typedef PriorityQueueHeap, u32, std::greater > OpenQueue; static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal, Grid& grid, OpenQueue& queue, const Grid& costGrid) { u32 dg = falloff * costGrid.get(i, j); if (diagonal) dg = (dg * 362) / 256; // Stop if new cost g=pg-dg is not better than previous value for that tile // (arranged to avoid underflow if pg < dg) if (pg <= grid.get(i, j) + dg) return; u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor grid.set(i, j, g); OpenQueue::Item tile = { std::make_pair(i, j), g }; queue.push(tile); } static void FloodFill(Grid& grid, Grid& costGrid, OpenQueue& openTiles, u32 falloff) { u16 tilesW = grid.m_W; u16 tilesH = grid.m_H; while (!openTiles.empty()) { OpenQueue::Item tile = openTiles.pop(); // Process neighbours (if they're not off the edge of the map) u16 x = tile.id.first; u16 z = tile.id.second; if (x > 0) ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid); if (x < tilesW-1) ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid); if (z > 0) ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid); if (z < tilesH-1) ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid); if (x > 0 && z > 0) ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid); if (x > 0 && z < tilesH-1) ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid); if (x < tilesW-1 && z > 0) ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid); if (x < tilesW-1 && z < tilesH-1) ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid); } } void CCmpTerritoryManager::CalculateTerritories() { if (m_Territories) return; PROFILE("CalculateTerritories"); CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); // If the terrain hasn't been loaded (e.g. this is called during map initialisation), // abort the computation (and assume callers can cope with m_Territories == NULL) if (!cmpTerrain->IsLoaded()) return; u16 tilesW = cmpTerrain->GetTilesPerSide(); u16 tilesH = cmpTerrain->GetTilesPerSide(); m_Territories = new Grid(tilesW, tilesH); // Compute terrain-passability-dependent costs per tile Grid influenceGrid(tilesW, tilesH); CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default"); const Grid& passGrid = cmpPathfinder->GetPassabilityGrid(); for (u16 j = 0; j < tilesH; ++j) { for (u16 i = 0; i < tilesW; ++i) { u16 g = passGrid.get(i, j); u8 cost; if (g & passClassUnrestricted) cost = 255; // off the world; use maximum cost else if (g & passClassDefault) cost = m_ImpassableCost; else cost = 1; influenceGrid.set(i, j, cost); } } // Find all territory influence entities CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence); // Allow influence entities to override the terrain costs RasteriseInfluences(influences, influenceGrid); // Split influence entities into per-player lists, ignoring any with invalid properties std::map > influenceEntities; std::vector rootInfluenceEntities; for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it) { // Ignore any with no weight or radius (to avoid divide-by-zero later) ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast(it->second); if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0) continue; CmpPtr cmpOwnership(GetSimContext(), it->first); if (cmpOwnership.null()) continue; // Ignore Gaia and unassigned player_id_t owner = cmpOwnership->GetOwner(); if (owner <= 0) continue; // We only have 7 bits to store tile ownership, so ignore unrepresentable players if (owner > TERRITORY_PLAYER_MASK) continue; // Ignore if invalid position CmpPtr cmpPosition(GetSimContext(), it->first); if (cmpPosition.null() || !cmpPosition->IsInWorld()) continue; influenceEntities[owner].push_back(it->first); if (cmpTerritoryInfluence->IsRoot()) rootInfluenceEntities.push_back(it->first); } // For each player, store the sum of influences on each tile std::vector > > playerGrids; // TODO: this is a large waste of memory; we don't really need to store // all the intermediate grids for (std::map >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it) { Grid playerGrid(tilesW, tilesH); std::vector& ents = it->second; for (std::vector::iterator eit = ents.begin(); eit != ents.end(); ++eit) { // Compute the influence map of the current entity, then add it to the player grid Grid entityGrid(tilesW, tilesH); CmpPtr cmpPosition(GetSimContext(), *eit); CFixedVector2D pos = cmpPosition->GetPosition2D(); u16 i = (u16)clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1); u16 j = (u16)clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1); CmpPtr cmpTerritoryInfluence(GetSimContext(), *eit); u32 weight = cmpTerritoryInfluence->GetWeight(); u32 radius = cmpTerritoryInfluence->GetRadius() / CELL_SIZE; u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero // TODO: we should have some maximum value on weight, to avoid overflow // when doing all the sums // Initialise the tile under the entity entityGrid.set(i, j, weight); OpenQueue openTiles; OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), weight }; openTiles.push(tile); // Expand influences outwards FloodFill(entityGrid, influenceGrid, openTiles, falloff); // TODO: we should do a sparse grid and only add the non-zero regions, for performance for (u16 j = 0; j < entityGrid.m_H; ++j) for (u16 i = 0; i < entityGrid.m_W; ++i) playerGrid.set(i, j, playerGrid.get(i, j) + entityGrid.get(i, j)); } playerGrids.push_back(std::make_pair(it->first, playerGrid)); } // Set m_Territories to the player ID with the highest influence for each tile for (u16 j = 0; j < tilesH; ++j) { for (u16 i = 0; i < tilesW; ++i) { u32 bestWeight = 0; for (size_t k = 0; k < playerGrids.size(); ++k) { u32 w = playerGrids[k].second.get(i, j); if (w > bestWeight) { player_id_t id = playerGrids[k].first; m_Territories->set(i, j, (u8)id); bestWeight = w; } } } } // Detect territories connected to a 'root' influence (typically a civ center) // belonging to their player, and mark them with the connected flag for (std::vector::iterator it = rootInfluenceEntities.begin(); it != rootInfluenceEntities.end(); ++it) { // (These components must be valid else the entities wouldn't be added to this list) CmpPtr cmpOwnership(GetSimContext(), *it); CmpPtr cmpPosition(GetSimContext(), *it); CFixedVector2D pos = cmpPosition->GetPosition2D(); u16 i = (u16)clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1); u16 j = (u16)clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1); u8 owner = (u8)cmpOwnership->GetOwner(); if (m_Territories->get(i, j) != owner) continue; // TODO: would be nice to refactor some of the many flood fill // algorithms in this component Grid& grid = *m_Territories; u16 maxi = (u16)(grid.m_W-1); u16 maxj = (u16)(grid.m_H-1); std::vector > tileStack; #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair(i, j)); ) MARK_AND_PUSH(i, j); while (!tileStack.empty()) { int ti = tileStack.back().first; int tj = tileStack.back().second; tileStack.pop_back(); if (ti > 0 && grid.get(ti-1, tj) == owner) MARK_AND_PUSH(ti-1, tj); if (ti < maxi && grid.get(ti+1, tj) == owner) MARK_AND_PUSH(ti+1, tj); if (tj > 0 && grid.get(ti, tj-1) == owner) MARK_AND_PUSH(ti, tj-1); if (tj < maxj && grid.get(ti, tj+1) == owner) MARK_AND_PUSH(ti, tj+1); if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner) MARK_AND_PUSH(ti-1, tj-1); if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner) MARK_AND_PUSH(ti-1, tj+1); if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner) MARK_AND_PUSH(ti+1, tj-1); if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner) MARK_AND_PUSH(ti+1, tj+1); } #undef MARK_AND_PUSH } } /** * Compute the tile indexes on the grid nearest to a given point */ static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) { i = (u16)clamp((x / (int)CELL_SIZE).ToInt_RoundToZero(), 0, w-1); j = (u16)clamp((z / (int)CELL_SIZE).ToInt_RoundToZero(), 0, h-1); } /** * Returns the position of the center of the given tile */ static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) { x = entity_pos_t::FromInt(i*(int)CELL_SIZE + (int)CELL_SIZE/2); z = entity_pos_t::FromInt(j*(int)CELL_SIZE + (int)CELL_SIZE/2); } // TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid) { for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it) { ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast(it->second); i32 cost = cmpTerritoryInfluence->GetCost(); if (cost == -1) continue; CmpPtr cmpObstruction(GetSimContext(), it->first); if (cmpObstruction.null()) continue; ICmpObstructionManager::ObstructionSquare square; if (!cmpObstruction->GetObstructionSquare(square)) continue; CFixedVector2D halfSize(square.hw, square.hh); CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize); u16 i0, j0, i1, j1; NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H); NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H); for (u16 j = j0; j <= j1; ++j) { for (u16 i = i0; i <= i1; ++i) { entity_pos_t x, z; TileCenter(i, j, x, z); if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize)) grid.set(i, j, (u8)cost); } } } } std::vector CCmpTerritoryManager::ComputeBoundaries() { PROFILE("ComputeBoundaries"); std::vector boundaries; CalculateTerritories(); ENSURE(m_Territories); // Copy the territories grid so we can mess with it Grid grid (*m_Territories); // Some constants for the border walk CVector2D edgeOffsets[] = { CVector2D(0.5f, 0.0f), CVector2D(1.0f, 0.5f), CVector2D(0.5f, 1.0f), CVector2D(0.0f, 0.5f) }; // Try to find an assigned tile for (u16 j = 0; j < grid.m_H; ++j) { for (u16 i = 0; i < grid.m_W; ++i) { u8 owner = grid.get(i, j); if (owner) { // Found the first tile (which must be the lowest j value of any non-zero tile); // start at the bottom edge of it and chase anticlockwise around the border until // we reach the starting point again boundaries.push_back(TerritoryBoundary()); boundaries.back().connected = (owner & TERRITORY_CONNECTED_MASK) != 0; boundaries.back().owner = (owner & TERRITORY_PLAYER_MASK); std::vector& points = boundaries.back().points; u8 dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left u8 cdir = dir; u16 ci = i, cj = j; u16 maxi = (u16)(grid.m_W-1); u16 maxj = (u16)(grid.m_H-1); while (true) { points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * CELL_SIZE); // Given that we're on an edge on a continuous boundary and aiming anticlockwise, // we can either carry on straight or turn left or turn right, so examine each // of the three possible cases (depending on initial direction): switch (cdir) { case 0: if (ci < maxi && cj > 0 && grid.get(ci+1, cj-1) == owner) { ++ci; --cj; cdir = 3; } else if (ci < maxi && grid.get(ci+1, cj) == owner) ++ci; else cdir = 1; break; case 1: if (ci < maxi && cj < maxj && grid.get(ci+1, cj+1) == owner) { ++ci; ++cj; cdir = 0; } else if (cj < maxj && grid.get(ci, cj+1) == owner) ++cj; else cdir = 2; break; case 2: if (ci > 0 && cj < maxj && grid.get(ci-1, cj+1) == owner) { --ci; ++cj; cdir = 1; } else if (ci > 0 && grid.get(ci-1, cj) == owner) --ci; else cdir = 3; break; case 3: if (ci > 0 && cj > 0 && grid.get(ci-1, cj-1) == owner) { --ci; --cj; cdir = 2; } else if (cj > 0 && grid.get(ci, cj-1) == owner) --cj; else cdir = 0; break; } // Stop when we've reached the starting point again if (ci == i && cj == j && cdir == dir) break; } // Zero out this whole territory with a simple flood fill, so we don't // process it a second time std::vector > tileStack; #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); ) MARK_AND_PUSH(i, j); while (!tileStack.empty()) { int ti = tileStack.back().first; int tj = tileStack.back().second; tileStack.pop_back(); if (ti > 0 && grid.get(ti-1, tj) == owner) MARK_AND_PUSH(ti-1, tj); if (ti < maxi && grid.get(ti+1, tj) == owner) MARK_AND_PUSH(ti+1, tj); if (tj > 0 && grid.get(ti, tj-1) == owner) MARK_AND_PUSH(ti, tj-1); if (tj < maxj && grid.get(ti, tj+1) == owner) MARK_AND_PUSH(ti, tj+1); if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner) MARK_AND_PUSH(ti-1, tj-1); if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner) MARK_AND_PUSH(ti-1, tj+1); if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner) MARK_AND_PUSH(ti+1, tj-1); if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner) MARK_AND_PUSH(ti+1, tj+1); } #undef MARK_AND_PUSH } } } return boundaries; } void CCmpTerritoryManager::UpdateBoundaryLines() { PROFILE("update boundary lines"); m_BoundaryLines.clear(); if (!CRenderer::IsInitialised()) return; std::vector boundaries = ComputeBoundaries(); CTextureProperties texturePropsBase("art/textures/misc/territory_border.png"); texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsBase.SetMaxAnisotropy(2.f); CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png"); texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsMask.SetMaxAnisotropy(2.f); CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (cmpTerrain.null()) return; CTerrain* terrain = cmpTerrain->GetCTerrain(); CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); if (cmpPlayerManager.null()) return; for (size_t i = 0; i < boundaries.size(); ++i) { if (boundaries[i].points.empty()) continue; CColor color(1, 0, 1, 1); CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner)); if (!cmpPlayer.null()) color = cmpPlayer->GetColour(); m_BoundaryLines.push_back(SBoundaryLine()); m_BoundaryLines.back().connected = boundaries[i].connected; m_BoundaryLines.back().color = color; m_BoundaryLines.back().overlay.m_Terrain = terrain; m_BoundaryLines.back().overlay.m_TextureBase = textureBase; m_BoundaryLines.back().overlay.m_TextureMask = textureMask; m_BoundaryLines.back().overlay.m_Color = color; m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness; SimRender::SmoothPointsAverage(boundaries[i].points, true); SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation); std::vector& points = m_BoundaryLines.back().overlay.m_Coords; for (size_t j = 0; j < boundaries[i].points.size(); ++j) { points.push_back(boundaries[i].points[j].X); points.push_back(boundaries[i].points[j].Y); } } } void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset)) { m_AnimTime += frameTime; if (m_BoundaryLinesDirty) { UpdateBoundaryLines(); m_BoundaryLinesDirty = false; } for (size_t i = 0; i < m_BoundaryLines.size(); ++i) { if (!m_BoundaryLines[i].connected) { CColor c = m_BoundaryLines[i].color; c.a *= 0.2f + 0.8f * fabsf((float)cos(m_AnimTime * M_PI)); // TODO: should let artists tweak this m_BoundaryLines[i].overlay.m_Color = c; } } } void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) { for (size_t i = 0; i < m_BoundaryLines.size(); ++i) collector.Submit(&m_BoundaryLines[i].overlay); } player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z) { u16 i, j; CalculateTerritories(); if (!m_Territories) return 0; NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; } bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z) { u16 i, j; CalculateTerritories(); if (!m_Territories) return false; NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0; } void TerritoryOverlay::StartRender() { m_TerritoryManager.CalculateTerritories(); } void TerritoryOverlay::ProcessTile(ssize_t i, ssize_t j) { if (!m_TerritoryManager.m_Territories) return; u8 id = m_TerritoryManager.m_Territories->get((int)i, (int)j); float a = 0.2f; switch (id) { case 0: break; case 1: RenderTile(CColor(1, 0, 0, a), false); break; case 2: RenderTile(CColor(0, 1, 0, a), false); break; case 3: RenderTile(CColor(0, 0, 1, a), false); break; case 4: RenderTile(CColor(1, 1, 0, a), false); break; case 5: RenderTile(CColor(0, 1, 1, a), false); break; case 6: RenderTile(CColor(1, 0, 1, a), false); break; default: RenderTile(CColor(1, 1, 1, a), false); break; } }