diff --git a/binaries/data/mods/public/simulation/templates/special/territory_block.xml b/binaries/data/mods/public/simulation/templates/special/territory_block.xml
index de25a9cc24..45d3ce1b1a 100644
--- a/binaries/data/mods/public/simulation/templates/special/territory_block.xml
+++ b/binaries/data/mods/public/simulation/templates/special/territory_block.xml
@@ -28,6 +28,8 @@
structures/hellenes/wall_med.xml
- 100
+ 64
+ 0
+ 0
diff --git a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
index 0db6599291..206c2451e9 100644
--- a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
+++ b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
@@ -29,5 +29,7 @@
0
+ 0
+ 0
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
index ae05252c30..a7f54d5daf 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
@@ -67,4 +67,8 @@
units/{civ}_support_female_citizen
+
+ 256
+ 65536
+
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
index fd28e004f7..494213339e 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
@@ -54,4 +54,8 @@
units/{civ}_support_female_citizen
+
+ 64
+ 65536
+
diff --git a/source/simulation2/components/CCmpTerritoryInfluence.cpp b/source/simulation2/components/CCmpTerritoryInfluence.cpp
index ae1904a5d4..dd7ec0a9de 100644
--- a/source/simulation2/components/CCmpTerritoryInfluence.cpp
+++ b/source/simulation2/components/CCmpTerritoryInfluence.cpp
@@ -29,19 +29,37 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence)
- u8 m_Cost;
+ int m_Cost;
+ u32 m_Weight;
+ u32 m_Radius;
static std::string GetSchema()
{
return
- ""
+ ""
+ ""
+ ""
+ "255"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
- m_Cost = paramNode.GetChild("OverrideCost").ToInt();
+ if (paramNode.GetChild("OverrideCost").IsOk())
+ m_Cost = paramNode.GetChild("OverrideCost").ToInt();
+ else
+ m_Cost = -1;
+
+ m_Weight = paramNode.GetChild("Weight").ToInt();
+ m_Radius = paramNode.GetChild("Radius").ToInt();
}
virtual void Deinit()
@@ -57,11 +75,20 @@ public:
Init(paramNode);
}
- virtual u8 GetCost()
+ virtual int GetCost()
{
return m_Cost;
}
+ virtual u32 GetWeight()
+ {
+ return m_Weight;
+ }
+
+ virtual u32 GetRadius()
+ {
+ return m_Radius;
+ }
};
REGISTER_COMPONENT_TYPE(TerritoryInfluence)
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
index a167d079ab..18b0f92187 100644
--- a/source/simulation2/components/CCmpTerritoryManager.cpp
+++ b/source/simulation2/components/CCmpTerritoryManager.cpp
@@ -27,6 +27,7 @@
#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/ICmpPosition.h"
#include "simulation2/components/ICmpSettlement.h"
@@ -167,48 +168,72 @@ public:
* 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(Grid& grid);
+ void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid);
};
REGISTER_COMPONENT_TYPE(TerritoryManager)
/*
-
-We compute territory influences with a kind of best-first search:
- 1) Initialise an 'open' list with tiles that contain settlements (annotated with
- territory ID) with initial cost 0
- 2) Pick the lowest cost tile from 'item'
- 3) For each neighbour which has not already been assigned to a territory,
- assign it to this territory and compute its new cost (effectively the
- distance from the associated settlement) and add to 'open'
- 4) Go to 2 until 'open' is empty
-
+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> OpenQueue;
+typedef PriorityQueueHeap, u32, std::greater > OpenQueue;
-static void ProcessNeighbour(u8 pid, u16 i, u16 j, u32 pg, bool diagonal,
- Grid& grid, OpenQueue& queue, const Grid& influenceGrid)
+static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
+ Grid& grid, OpenQueue& queue, const Grid& costGrid)
{
- // Ignore tiles that are already claimed
- u8 id = grid.get(i, j);
- if (id)
+ 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;
- // Base cost for moving onto this tile
- u32 dg = diagonal ? 362 : 256;
+ u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
- // Adjust cost based on this tile's influences
- dg *= influenceGrid.get(i, j);
-
- u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
-
- grid.set(i, j, pid);
+ 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)
+{
+ u32 tilesW = grid.m_W;
+ u32 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, x-1, z, tile.rank, false, grid, openTiles, costGrid);
+ if (x < tilesW-1)
+ ProcessNeighbour(falloff, x+1, z, tile.rank, false, grid, openTiles, costGrid);
+ if (z > 0)
+ ProcessNeighbour(falloff, x, z-1, tile.rank, false, grid, openTiles, costGrid);
+ if (z < tilesH-1)
+ ProcessNeighbour(falloff, x, z+1, tile.rank, false, grid, openTiles, costGrid);
+ if (x > 0 && z > 0)
+ ProcessNeighbour(falloff, x-1, z-1, tile.rank, true, grid, openTiles, costGrid);
+ if (x > 0 && z < tilesH-1)
+ ProcessNeighbour(falloff, x-1, z+1, tile.rank, true, grid, openTiles, costGrid);
+ if (x < tilesW-1 && z > 0)
+ ProcessNeighbour(falloff, x+1, z-1, tile.rank, true, grid, openTiles, costGrid);
+ if (x < tilesW-1 && z < tilesH-1)
+ ProcessNeighbour(falloff, x+1, z+1, tile.rank, true, grid, openTiles, costGrid);
+ }
+}
+
void CCmpTerritoryManager::CalculateTerritories()
{
PROFILE("CalculateTerritories");
@@ -220,98 +245,124 @@ void CCmpTerritoryManager::CalculateTerritories()
uint32_t tilesW = cmpTerrain->GetVerticesPerSide() - 1;
uint32_t tilesH = cmpTerrain->GetVerticesPerSide() - 1;
- Grid influenceGrid(tilesW, tilesH);
-
- RasteriseInfluences(influenceGrid);
-
SAFE_DELETE(m_Territories);
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 passClass = cmpPathfinder->GetPassabilityClass("default");
const Grid& passGrid = cmpPathfinder->GetPassabilityGrid();
-
- // Adjust influenceGrid so it contains terrain-passability-dependent costs,
- // unless overridden by existing values in influenceGrid
for (u32 j = 0; j < tilesH; ++j)
{
for (u32 i = 0; i < tilesW; ++i)
{
u8 cost;
- u8 inflCost = influenceGrid.get(i, j);
- if (inflCost)
- {
- cost = inflCost-1; // undo RasteriseInfluences's offset
- }
+ if (passGrid.get(i, j) & passClass)
+ cost = 4; // TODO: should come from some XML file
else
- {
- if (passGrid.get(i, j) & passClass)
- cost = 100;
- else
- cost = 1;
- }
+ cost = 1;
influenceGrid.set(i, j, cost);
}
}
- OpenQueue openTiles;
+ // Find all territory influence entities
+ CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
- // Initialise open list with all settlements
+ // Allow influence entities to override the terrain costs
+ RasteriseInfluences(influences, influenceGrid);
- CComponentManager::InterfaceList settlements = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_Settlement);
- u8 id = 1;
- for (CComponentManager::InterfaceList::iterator it = settlements.begin(); it != settlements.end(); ++it)
+ // Split influence entities into per-player lists, ignoring any with invalid properties
+ std::map > influenceEntities;
+ for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
{
- entity_id_t settlement = it->first;
- CmpPtr cmpPosition(GetSimContext(), settlement);
+ // 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;
+
+ // Ignore if invalid position
+ CmpPtr cmpPosition(GetSimContext(), it->first);
if (cmpPosition.null() || !cmpPosition->IsInWorld())
continue;
- // TODO: maybe we need to ignore settlements with owner -1,
- // since they're probably destroyed
-
- CFixedVector2D pos = cmpPosition->GetPosition2D();
- int i = clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesW-1);
- int j = clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesH-1);
-
- // Must avoid duplicates in the priority queue; ignore the settlement
- // if there's already one on that tile
- if (!m_Territories->get(i, j))
- {
- m_Territories->set(i, j, id);
- OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), 0 };
- openTiles.push(tile);
- }
-
- id += 1;
+ influenceEntities[owner].push_back(it->first);
}
- while (!openTiles.empty())
+ // 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)
{
- OpenQueue::Item tile = openTiles.pop();
+ Grid playerGrid(tilesW, tilesH);
- // Get current tile's territory ID
- u8 tid = m_Territories->get(tile.id.first, tile.id.second);
+ 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
- // 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(tid, x-1, z, tile.rank, false, *m_Territories, openTiles, influenceGrid);
- if (x < tilesW-1)
- ProcessNeighbour(tid, x+1, z, tile.rank, false, *m_Territories, openTiles, influenceGrid);
- if (z > 0)
- ProcessNeighbour(tid, x, z-1, tile.rank, false, *m_Territories, openTiles, influenceGrid);
- if (z < tilesH-1)
- ProcessNeighbour(tid, x, z+1, tile.rank, false, *m_Territories, openTiles, influenceGrid);
- if (x > 0 && z > 0)
- ProcessNeighbour(tid, x-1, z-1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
- if (x > 0 && z < tilesH-1)
- ProcessNeighbour(tid, x-1, z+1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
- if (x < tilesW-1 && z > 0)
- ProcessNeighbour(tid, x+1, z-1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
- if (x < tilesW-1 && z < tilesH-1)
- ProcessNeighbour(tid, x+1, z+1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
+ Grid entityGrid(tilesW, tilesH);
+
+ CmpPtr cmpPosition(GetSimContext(), *eit);
+ CFixedVector2D pos = cmpPosition->GetPosition2D();
+ int i = clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesW-1);
+ int j = clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)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;
+ }
+ }
+ }
}
}
@@ -336,13 +387,16 @@ static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
// TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp
-void CCmpTerritoryManager::RasteriseInfluences(Grid& grid)
+void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid)
{
- CComponentManager::InterfaceList infls = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
{
ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast(it->second);
+ int cost = cmpTerritoryInfluence->GetCost();
+ if (cost == -1)
+ continue;
+
CmpPtr cmpObstruction(GetSimContext(), it->first);
if (cmpObstruction.null())
continue;
@@ -351,8 +405,6 @@ void CCmpTerritoryManager::RasteriseInfluences(Grid& grid)
if (!cmpObstruction->GetObstructionSquare(square))
continue;
- u8 cost = cmpTerritoryInfluence->GetCost();
-
CFixedVector2D halfSize(square.hw, square.hh);
CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
@@ -366,7 +418,7 @@ void CCmpTerritoryManager::RasteriseInfluences(Grid& grid)
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, cost+1);
+ grid.set(i, j, cost);
}
}
diff --git a/source/simulation2/components/ICmpTerritoryInfluence.h b/source/simulation2/components/ICmpTerritoryInfluence.h
index e760988a7f..7c6ab827cb 100644
--- a/source/simulation2/components/ICmpTerritoryInfluence.h
+++ b/source/simulation2/components/ICmpTerritoryInfluence.h
@@ -23,7 +23,16 @@
class ICmpTerritoryInfluence : public IComponent
{
public:
- virtual u8 GetCost() = 0;
+ /**
+ * Returns either -1 to indicate no special terrain cost, or a value
+ * in [0, 255] to indicate overriding the normal cost of the terrain
+ * under the entity's obstruction.
+ */
+ virtual int GetCost() = 0;
+
+ virtual u32 GetWeight() = 0;
+
+ virtual u32 GetRadius() = 0;
DECLARE_INTERFACE_TYPE(TerritoryInfluence)
};
diff --git a/source/simulation2/helpers/PriorityQueue.h b/source/simulation2/helpers/PriorityQueue.h
index 5ca2a171f6..9e7c8e37ee 100644
--- a/source/simulation2/helpers/PriorityQueue.h
+++ b/source/simulation2/helpers/PriorityQueue.h
@@ -29,14 +29,14 @@
#define PRIORITYQUEUE_DEBUG 1
#endif
-template
+template
struct QueueItemPriority
{
bool operator()(const Item& a, const Item& b)
{
- if (a.rank > b.rank) // higher costs are lower priority
+ if (CMP()(b.rank, a.rank)) // higher costs are lower priority
return true;
- if (a.rank < b.rank)
+ if (CMP()(a.rank, b.rank))
return false;
// Need to tie-break to get a consistent ordering
// TODO: Should probably tie-break on g or h or something, but don't bother for now
@@ -57,7 +57,7 @@ struct QueueItemPriority
* This is quite dreadfully slow in MSVC's debug STL implementation,
* so we shouldn't use it unless we reimplement the heap functions more efficiently.
*/
-template
+template >
class PriorityQueueHeap
{
public:
@@ -70,7 +70,7 @@ public:
void push(const Item& item)
{
m_Heap.push_back(item);
- push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority- ());
+ push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority
- ());
}
Item* find(ID id)
@@ -93,7 +93,7 @@ public:
ENSURE(m_Heap[n].rank > newrank);
#endif
m_Heap[n].rank = newrank;
- push_heap(m_Heap.begin(), m_Heap.begin()+n+1, QueueItemPriority
- ());
+ push_heap(m_Heap.begin(), m_Heap.begin()+n+1, QueueItemPriority
- ());
return;
}
}
@@ -105,7 +105,7 @@ public:
ENSURE(m_Heap.size());
#endif
Item r = m_Heap.front();
- pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority
- ());
+ pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority
- ());
m_Heap.pop_back();
return r;
}
@@ -130,7 +130,7 @@ public:
* It seems fractionally slower than a binary heap in optimised builds, but is
* much simpler and less susceptible to MSVC's painfully slow debug STL.
*/
-template
+template >
class PriorityQueueList
{
public:
@@ -171,7 +171,7 @@ public:
size_t bestidx = m_List.size()-1;
for (ssize_t i = (ssize_t)bestidx-1; i >= 0; --i)
{
- if (QueueItemPriority
- ()(best, m_List[i]))
+ if (QueueItemPriority
- ()(best, m_List[i]))
{
bestidx = i;
best = m_List[i];